@masumdev/markforge 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,
@@ -67,19 +68,29 @@ __export(src_exports, {
67
68
  inlineHtmlImages: () => inlineHtmlImages,
68
69
  loadConfig: () => loadConfig,
69
70
  markforge: () => compileMarkdown,
71
+ normalizeBackCover: () => normalizeBackCover,
72
+ normalizeCoverPage: () => normalizeCoverPage,
70
73
  normalizeHeaderFooter: () => normalizeHeaderFooter,
71
74
  normalizeHeaderFooterSlot: () => normalizeHeaderFooterSlot,
75
+ normalizeNumberHeadings: () => normalizeNumberHeadings,
76
+ normalizeSecurity: () => normalizeSecurity,
72
77
  normalizeSignatures: () => normalizeSignatures,
73
78
  normalizeWatermark: () => normalizeWatermark,
74
79
  parseInlineSpans: () => parseInlineSpans,
75
80
  parseMarginToTwip: () => parseMarginToTwip2,
81
+ parseMarkdown: () => parseMarkdownDocument,
76
82
  parseMarkdownDocument: () => parseMarkdownDocument,
83
+ renderBackCoverHtml: () => renderBackCoverHtml,
84
+ renderCoverPageHtml: () => renderCoverPageHtml,
77
85
  renderInlinesToHtml: () => renderInlinesToHtml,
86
+ renderMathToHtml: () => renderMathToHtml,
78
87
  renderMermaidToPng: () => renderMermaidToPng,
88
+ renderNodesToHtml: () => renderNodesToHtml,
79
89
  replaceDocumentTokens: () => replaceDocumentTokens,
80
90
  resolveDocumentConfig: () => resolveDocumentConfig,
81
91
  resolveImage: () => resolveImage,
82
92
  slugify: () => slugify,
93
+ startPreviewServer: () => startPreviewServer,
83
94
  tokenizeCodeLine: () => tokenizeCodeLine
84
95
  });
85
96
  module.exports = __toCommonJS(src_exports);
@@ -120,6 +131,26 @@ function parseInlineSpans(text) {
120
131
  remaining = remaining.slice(imgMatch[0].length);
121
132
  continue;
122
133
  }
134
+ const fnMatch = remaining.match(/^\[\^([\w-]+)\]/);
135
+ if (fnMatch) {
136
+ const fnId = fnMatch[1];
137
+ spans.push({
138
+ type: "footnoteRef",
139
+ content: fnId,
140
+ footnoteId: fnId
141
+ });
142
+ remaining = remaining.slice(fnMatch[0].length);
143
+ continue;
144
+ }
145
+ const mathMatch = remaining.match(/^\$([^$\n]+?)\$/);
146
+ if (mathMatch && !mathMatch[1].startsWith("$")) {
147
+ spans.push({
148
+ type: "mathInline",
149
+ content: mathMatch[1]
150
+ });
151
+ remaining = remaining.slice(mathMatch[0].length);
152
+ continue;
153
+ }
123
154
  const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
124
155
  if (linkMatch) {
125
156
  spans.push({
@@ -219,7 +250,7 @@ function parseInlineSpans(text) {
219
250
  remaining = remaining.slice(fullTag.length);
220
251
  continue;
221
252
  }
222
- const nextSpecial = remaining.search(/[\*\_\[\!`~<]/);
253
+ const nextSpecial = remaining.search(/[\*\_\[\!`~<\$]/);
223
254
  if (nextSpecial === -1) {
224
255
  spans.push({
225
256
  type: "text",
@@ -245,6 +276,35 @@ function parseInlineSpans(text) {
245
276
  function slugify(text) {
246
277
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
247
278
  }
279
+ function applyHeadingNumbering(nodes, tocEntries, options) {
280
+ const depth = options.depth ?? 3;
281
+ const skipH1 = options.skipH1 ?? false;
282
+ const prefix = options.prefix ?? "";
283
+ const counters = [0, 0, 0, 0, 0, 0];
284
+ for (const node of nodes) {
285
+ if (node.type === "heading" && node.level) {
286
+ const lvl = node.level;
287
+ if (lvl > depth) continue;
288
+ if (lvl === 1 && skipH1) continue;
289
+ const idx = lvl - 1;
290
+ counters[idx]++;
291
+ for (let c = idx + 1; c < counters.length; c++) {
292
+ counters[c] = 0;
293
+ }
294
+ const startIdx = skipH1 ? 1 : 0;
295
+ const parts = counters.slice(startIdx, idx + 1).filter((n) => n > 0);
296
+ const numberStr = parts.join(".") + ".";
297
+ const fullPrefix = prefix ? `${prefix} ${numberStr} ` : `${numberStr} `;
298
+ const originalText = node.text || "";
299
+ node.text = fullPrefix + originalText;
300
+ node.inlines = parseInlineSpans(node.text);
301
+ const toc = tocEntries.find((t) => t.id === node.id);
302
+ if (toc) {
303
+ toc.text = node.text;
304
+ }
305
+ }
306
+ }
307
+ }
248
308
  function parseMarkdownDocument(rawMarkdown) {
249
309
  const { data: frontmatter, content } = (0, import_gray_matter.default)(rawMarkdown);
250
310
  const metadata = frontmatter || {};
@@ -256,6 +316,7 @@ function parseMarkdownDocument(rawMarkdown) {
256
316
  const lines = cleanContent.split(/\r?\n/);
257
317
  const nodes = [];
258
318
  const tocEntries = [];
319
+ const footnoteDefs = [];
259
320
  let i = 0;
260
321
  while (i < lines.length) {
261
322
  const line = lines[i];
@@ -284,6 +345,29 @@ function parseMarkdownDocument(rawMarkdown) {
284
345
  i++;
285
346
  continue;
286
347
  }
348
+ if (line.trim().startsWith("$$")) {
349
+ const mathLines = [];
350
+ const singleLine = line.trim().match(/^\$\$(.+)\$\$$/);
351
+ if (singleLine) {
352
+ nodes.push({
353
+ type: "mathBlock",
354
+ text: singleLine[1].trim()
355
+ });
356
+ i++;
357
+ continue;
358
+ }
359
+ i++;
360
+ while (i < lines.length && !lines[i].trim().startsWith("$$")) {
361
+ mathLines.push(lines[i]);
362
+ i++;
363
+ }
364
+ if (i < lines.length) i++;
365
+ nodes.push({
366
+ type: "mathBlock",
367
+ text: mathLines.join("\n").trim()
368
+ });
369
+ continue;
370
+ }
287
371
  const codeBlockMatch = line.match(/^```(\w+)?/);
288
372
  if (codeBlockMatch) {
289
373
  const language = (codeBlockMatch[1] || "text").trim().toLowerCase();
@@ -310,6 +394,100 @@ function parseMarkdownDocument(rawMarkdown) {
310
394
  }
311
395
  continue;
312
396
  }
397
+ const colsMatch = line.trim().match(/^:::columns(?:\s+\[?([\w\s=.-]+)\]?)?$/i);
398
+ if (colsMatch) {
399
+ const attrStr = colsMatch[1] || "";
400
+ let colsCount = 2;
401
+ let colGap = "1.5rem";
402
+ if (attrStr) {
403
+ const numMatch = attrStr.trim().match(/^(\d+)$/);
404
+ const cMatch = attrStr.match(/cols=(\d+)/i) || attrStr.match(/columns=(\d+)/i);
405
+ const gMatch = attrStr.match(/gap=([^\s]+)/i);
406
+ if (numMatch) colsCount = parseInt(numMatch[1], 10);
407
+ else if (cMatch) colsCount = parseInt(cMatch[1], 10);
408
+ if (gMatch) colGap = gMatch[1];
409
+ }
410
+ const columnNodes = [];
411
+ let currentColumnLines = [];
412
+ let inColBlock = false;
413
+ i++;
414
+ while (i < lines.length) {
415
+ const curLine = lines[i];
416
+ const trimmed = curLine.trim();
417
+ if (/^:::col(?:umn)?$/i.test(trimmed)) {
418
+ if (currentColumnLines.length > 0) {
419
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
420
+ columnNodes.push({
421
+ type: "column",
422
+ children: subDoc.nodes
423
+ });
424
+ currentColumnLines = [];
425
+ }
426
+ inColBlock = true;
427
+ i++;
428
+ } else if (trimmed === ":::") {
429
+ if (inColBlock) {
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 = false;
439
+ i++;
440
+ } else {
441
+ if (currentColumnLines.length > 0) {
442
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
443
+ columnNodes.push({
444
+ type: "column",
445
+ children: subDoc.nodes
446
+ });
447
+ currentColumnLines = [];
448
+ }
449
+ i++;
450
+ break;
451
+ }
452
+ } else {
453
+ currentColumnLines.push(curLine);
454
+ i++;
455
+ }
456
+ }
457
+ if (currentColumnLines.length > 0) {
458
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
459
+ columnNodes.push({
460
+ type: "column",
461
+ children: subDoc.nodes
462
+ });
463
+ }
464
+ nodes.push({
465
+ type: "columns",
466
+ columnsCount: columnNodes.length > 0 ? columnNodes.length : colsCount,
467
+ columnGap: colGap,
468
+ children: columnNodes
469
+ });
470
+ continue;
471
+ }
472
+ const fnDefMatch = line.match(/^\[\^([\w-]+)\]:\s+(.+)$/);
473
+ if (fnDefMatch) {
474
+ const fnId = fnDefMatch[1];
475
+ const fnText = fnDefMatch[2].trim();
476
+ const inlines = parseInlineSpans(fnText);
477
+ footnoteDefs.push({
478
+ id: fnId,
479
+ text: fnText,
480
+ inlines
481
+ });
482
+ nodes.push({
483
+ type: "footnoteDef",
484
+ footnoteId: fnId,
485
+ text: fnText,
486
+ inlines
487
+ });
488
+ i++;
489
+ continue;
490
+ }
313
491
  const calloutMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i);
314
492
  if (calloutMatch) {
315
493
  const calloutType = calloutMatch[1].toUpperCase();
@@ -433,7 +611,7 @@ function parseMarkdownDocument(rawMarkdown) {
433
611
  continue;
434
612
  }
435
613
  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+/)) {
614
+ 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
615
  paraLines.push(lines[i]);
438
616
  i++;
439
617
  }
@@ -444,12 +622,19 @@ function parseMarkdownDocument(rawMarkdown) {
444
622
  inlines: parseInlineSpans(paraText)
445
623
  });
446
624
  }
625
+ if (metadata.numberHeadings) {
626
+ const opts = typeof metadata.numberHeadings === "boolean" ? { enabled: metadata.numberHeadings } : metadata.numberHeadings;
627
+ if (opts.enabled !== false) {
628
+ applyHeadingNumbering(nodes, tocEntries, opts);
629
+ }
630
+ }
447
631
  return {
448
632
  metadata,
449
633
  content: cleanContent,
450
634
  nodes,
451
635
  tocEntries,
452
- inlinedStyles
636
+ inlinedStyles,
637
+ footnoteDefs
453
638
  };
454
639
  }
455
640
 
@@ -515,9 +700,14 @@ async function resolveImage(src, baseDir = process.cwd()) {
515
700
  memoryImageCache.set(cacheKey, resolved2);
516
701
  return resolved2;
517
702
  }
518
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
703
+ let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
519
704
  if (!fs.existsSync(localPath)) {
520
- return null;
705
+ const cwdPath = path.resolve(process.cwd(), src);
706
+ if (fs.existsSync(cwdPath)) {
707
+ localPath = cwdPath;
708
+ } else {
709
+ return null;
710
+ }
521
711
  }
522
712
  const buffer = fs.readFileSync(localPath);
523
713
  const mimeType = getMimeType(localPath);
@@ -922,6 +1112,8 @@ var path4 = __toESM(require("path"));
922
1112
  var os = __toESM(require("os"));
923
1113
  var import_node_url2 = require("url");
924
1114
  var import_node_child_process = require("child_process");
1115
+ var import_pdf_lib = require("pdf-lib");
1116
+ var import_pdf_encrypt = require("@pdfsmaller/pdf-encrypt");
925
1117
 
926
1118
  // src/core/html/htmlBuilder.ts
927
1119
  var fs3 = __toESM(require("fs"));
@@ -1164,6 +1356,12 @@ var DEFAULT_CONFIG = {
1164
1356
  },
1165
1357
  toc: false,
1166
1358
  watermark: void 0,
1359
+ signatures: void 0,
1360
+ coverPage: void 0,
1361
+ backCover: void 0,
1362
+ numberHeadings: void 0,
1363
+ security: void 0,
1364
+ math: true,
1167
1365
  embedImages: true,
1168
1366
  metadata: void 0,
1169
1367
  watch: false,
@@ -1320,7 +1518,8 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1320
1518
  return str;
1321
1519
  }
1322
1520
  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 || "");
1521
+ const currentYear = meta.year || (/* @__PURE__ */ new Date()).getFullYear().toString();
1522
+ 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(/\{year\}/gi, currentYear).replace(/\{company\}/gi, meta.company || "");
1324
1523
  }
1325
1524
  function normalizeWatermark(rawWatermark) {
1326
1525
  if (!rawWatermark) {
@@ -1457,6 +1656,129 @@ function normalizeSignatures(raw, meta = {}) {
1457
1656
  spacingBeforeTwip
1458
1657
  };
1459
1658
  }
1659
+ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1660
+ if (!rawCover) return void 0;
1661
+ const cfg = typeof rawCover === "object" ? rawCover : {};
1662
+ if (cfg.enabled === false) return void 0;
1663
+ const preset = cfg.preset || "modern";
1664
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title || "Document Title";
1665
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle;
1666
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author;
1667
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1668
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version;
1669
+ let dateStr;
1670
+ if (typeof cfg.date === "string") {
1671
+ dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
1672
+ } else if (cfg.date === true) {
1673
+ dateStr = tokenCtx.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1674
+ } else {
1675
+ dateStr = tokenCtx.date;
1676
+ }
1677
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1678
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1679
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1680
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1681
+ const logoWidth = cfg.logoWidth;
1682
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1683
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1684
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1685
+ return {
1686
+ enabled: true,
1687
+ preset,
1688
+ title,
1689
+ subtitle,
1690
+ author,
1691
+ company,
1692
+ version,
1693
+ date: dateStr,
1694
+ badge,
1695
+ badgeColor,
1696
+ badgeTextColor,
1697
+ logo,
1698
+ logoWidth,
1699
+ bgGradient,
1700
+ textColor,
1701
+ footerText
1702
+ };
1703
+ }
1704
+ function normalizeBackCover(rawBack, tokenCtx = {}) {
1705
+ if (!rawBack) return void 0;
1706
+ const cfg = typeof rawBack === "object" ? rawBack : {};
1707
+ if (cfg.enabled === false) return void 0;
1708
+ const preset = cfg.preset || "modern";
1709
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
1710
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
1711
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1712
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1713
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1714
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1715
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1716
+ let socialMap;
1717
+ if (cfg.social && typeof cfg.social === "object") {
1718
+ socialMap = {};
1719
+ for (const [k, v] of Object.entries(cfg.social)) {
1720
+ if (typeof v === "string") {
1721
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1722
+ }
1723
+ }
1724
+ }
1725
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1726
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
1727
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1728
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1729
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1730
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1731
+ const logoWidth = cfg.logoWidth;
1732
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1733
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1734
+ return {
1735
+ enabled: true,
1736
+ preset,
1737
+ title,
1738
+ subtitle,
1739
+ company,
1740
+ address,
1741
+ email,
1742
+ phone,
1743
+ website,
1744
+ social: socialMap,
1745
+ copyright,
1746
+ badge,
1747
+ badgeColor,
1748
+ badgeTextColor,
1749
+ logo,
1750
+ logoWidth,
1751
+ bgGradient,
1752
+ textColor
1753
+ };
1754
+ }
1755
+ function normalizeNumberHeadings(raw) {
1756
+ if (raw === void 0 || raw === false) return void 0;
1757
+ if (raw === true) {
1758
+ return { enabled: true, depth: 3, skipH1: false, prefix: "" };
1759
+ }
1760
+ if (typeof raw === "object") {
1761
+ const obj = raw;
1762
+ if (obj.enabled === false) return void 0;
1763
+ return {
1764
+ enabled: true,
1765
+ depth: obj.depth ?? 3,
1766
+ skipH1: obj.skipH1 ?? false,
1767
+ prefix: obj.prefix ?? ""
1768
+ };
1769
+ }
1770
+ return void 0;
1771
+ }
1772
+ function normalizeSecurity(raw) {
1773
+ if (!raw) return void 0;
1774
+ const sec = raw;
1775
+ if (!sec.userPassword && !sec.ownerPassword && !sec.permissions) return void 0;
1776
+ return {
1777
+ userPassword: sec.userPassword,
1778
+ ownerPassword: sec.ownerPassword,
1779
+ permissions: sec.permissions
1780
+ };
1781
+ }
1460
1782
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1461
1783
  const configMeta = userConfig.metadata || {};
1462
1784
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1499,6 +1821,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1499
1821
  const watermark = normalizeWatermark(rawWatermark);
1500
1822
  const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1501
1823
  const signatures = normalizeSignatures(rawSignatures, tokenContext);
1824
+ const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
1825
+ const coverPage = normalizeCoverPage(rawCover, tokenContext);
1826
+ const rawBack = mergedMeta.backCover !== void 0 ? mergedMeta.backCover : userConfig.backCover;
1827
+ const backCover = normalizeBackCover(rawBack, tokenContext);
1828
+ const rawNumberHeadings = mergedMeta.numberHeadings !== void 0 ? mergedMeta.numberHeadings : userConfig.numberHeadings;
1829
+ const numberHeadings = normalizeNumberHeadings(rawNumberHeadings);
1830
+ const rawSecurity = mergedMeta.security || userConfig.security;
1831
+ const security = normalizeSecurity(rawSecurity);
1832
+ const math = mergedMeta.math !== false && userConfig.math !== false;
1502
1833
  const cssList = [];
1503
1834
  const addCss = (item) => {
1504
1835
  if (!item) return;
@@ -1528,6 +1859,11 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1528
1859
  toc,
1529
1860
  signatures,
1530
1861
  watermark,
1862
+ coverPage,
1863
+ backCover,
1864
+ numberHeadings,
1865
+ security,
1866
+ math,
1531
1867
  css: cssList,
1532
1868
  embedImages,
1533
1869
  bundleHtml,
@@ -1535,6 +1871,45 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1535
1871
  };
1536
1872
  }
1537
1873
 
1874
+ // src/core/math/mathRenderer.ts
1875
+ var import_katex = __toESM(require("katex"));
1876
+ function renderMathToHtml(latex, displayMode = false) {
1877
+ try {
1878
+ return import_katex.default.renderToString(latex.trim(), {
1879
+ displayMode,
1880
+ throwOnError: false,
1881
+ output: "htmlAndMathml",
1882
+ strict: false
1883
+ });
1884
+ } catch {
1885
+ return `<span class="katex-fallback">${latex}</span>`;
1886
+ }
1887
+ }
1888
+ var KATEX_INLINE_CSS = `
1889
+ .katex { font: normal 1.21em KaTeX_Main, Times New Roman, serif; line-height: 1.2; text-indent: 0; text-rendering: auto; border-color: currentColor; }
1890
+ .katex * { -ms-high-contrast-adjust: none !important; }
1891
+ .katex .katex-html { display: inline-block; }
1892
+ .katex .katex-mathml { clip: rect(1px, 1px, 1px, 1px); border: 0; height: 1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
1893
+ .katex-display { display: block; margin: 1em 0; text-align: center; }
1894
+ .katex-display > .katex { display: inline-block; text-align: initial; }
1895
+ .katex .base { position: relative; white-space: nowrap; width: min-content; }
1896
+ .katex .strut { display: inline-block; }
1897
+ .katex .mord { display: inline-block; }
1898
+ .katex .mbin { display: inline-block; }
1899
+ .katex .mrel { display: inline-block; }
1900
+ .katex .mopen { display: inline-block; }
1901
+ .katex .mclose { display: inline-block; }
1902
+ .katex .mpunct { display: inline-block; }
1903
+ .katex .minner { display: inline-block; }
1904
+ .katex .mop { display: inline-block; }
1905
+ .katex .frac-line { width: 100%; border-bottom-style: solid; }
1906
+ .katex .vlist-t { display: inline-table; table-layout: fixed; }
1907
+ .katex .vlist-r { display: table-row; }
1908
+ .katex .vlist { display: table-cell; vertical-align: bottom; position: relative; }
1909
+ .katex .msupsub { text-align: left; }
1910
+ .katex .sqrt > .root { margin-left: 0.27777778em; margin-right: -0.55555556em; }
1911
+ `;
1912
+
1538
1913
  // src/core/html/htmlBuilder.ts
1539
1914
  function escapeHtml(str) {
1540
1915
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -1577,6 +1952,15 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1577
1952
  result += `<code>${escapeHtml(span.content)}</code>`;
1578
1953
  continue;
1579
1954
  }
1955
+ if (span.type === "mathInline") {
1956
+ result += renderMathToHtml(span.content, false);
1957
+ continue;
1958
+ }
1959
+ if (span.type === "footnoteRef") {
1960
+ const id = escapeHtml(span.footnoteId || span.content);
1961
+ result += `<sup><a href="#fn-${id}" id="fnref-${id}" class="markforge-fnref">[${escapeHtml(span.content)}]</a></sup>`;
1962
+ continue;
1963
+ }
1580
1964
  if (span.type === "htmlInline") {
1581
1965
  result += span.content;
1582
1966
  continue;
@@ -1585,66 +1969,9 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1585
1969
  }
1586
1970
  return result;
1587
1971
  }
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");
1972
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1601
1973
  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) {
1974
+ for (const node of nodes) {
1648
1975
  if (node.type === "heading") {
1649
1976
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1650
1977
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
@@ -1654,6 +1981,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1654
1981
  if (node.type === "paragraph") {
1655
1982
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1656
1983
  bodyHtml += ` <p>${inner}</p>
1984
+ `;
1985
+ continue;
1986
+ }
1987
+ if (node.type === "mathBlock") {
1988
+ bodyHtml += ` <div class="math-block">${renderMathToHtml(node.text || "", true)}</div>
1989
+ `;
1990
+ continue;
1991
+ }
1992
+ if (node.type === "columns") {
1993
+ const cols = node.columnsCount || 2;
1994
+ const gap = node.columnGap || "1.5rem";
1995
+ let colChildrenHtml = "";
1996
+ for (const col of node.children || []) {
1997
+ const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir);
1998
+ colChildrenHtml += ` <div class="markforge-col">
1999
+ ${colInner} </div>
2000
+ `;
2001
+ }
2002
+ bodyHtml += ` <div class="markforge-columns" style="--cols: ${cols}; --col-gap: ${gap};">
2003
+ ${colChildrenHtml} </div>
1657
2004
  `;
1658
2005
  continue;
1659
2006
  }
@@ -1742,95 +2089,522 @@ ${escapeHtml(node.text || "")}
1742
2089
  continue;
1743
2090
  }
1744
2091
  }
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;
2092
+ return bodyHtml;
2093
+ }
2094
+ async function renderCoverPageHtml(cover, baseDir = process.cwd()) {
2095
+ let logoHtml = "";
2096
+ if (cover.logo) {
2097
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
2098
+ const src = resolvedLogo ? resolvedLogo.dataUri : cover.logo;
2099
+ 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;";
2100
+ logoHtml = `<div class="cover-logo"><img src="${src}" alt="Logo" style="${widthStyle} object-fit: contain;" /></div>`;
1760
2101
  }
1761
- .document-container {
2102
+ const badgeHtml = cover.badge ? `<div class="cover-badge" style="${cover.badgeColor ? `background-color: ${cover.badgeColor};` : ""}${cover.badgeTextColor ? `color: ${cover.badgeTextColor};` : ""}">${escapeHtml(cover.badge)}</div>` : "";
2103
+ const titleHtml = `<h1 class="cover-title">${escapeHtml(cover.title)}</h1>`;
2104
+ const subtitleHtml = cover.subtitle ? `<div class="cover-subtitle">${escapeHtml(cover.subtitle)}</div>` : "";
2105
+ const metaItems = [];
2106
+ 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>`);
2107
+ 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>`);
2108
+ 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>`);
2109
+ 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>`);
2110
+ const metaHtml = metaItems.length > 0 ? `<div class="cover-meta">${metaItems.join("\n")}</div>` : "";
2111
+ const footerHtml = cover.footerText ? `<div class="cover-footer-text">${escapeHtml(cover.footerText)}</div>` : "";
2112
+ const css = `
2113
+ .markforge-cover {
2114
+ min-height: 100vh;
2115
+ box-sizing: border-box;
2116
+ display: flex;
2117
+ flex-direction: column;
2118
+ justify-content: space-between;
2119
+ padding: 4rem 3.5rem;
2120
+ page-break-after: always;
2121
+ break-after: page;
1762
2122
  position: relative;
1763
- z-index: 1;
2123
+ z-index: 2;
2124
+ background: ${cover.bgGradient || "#FFFFFF"};
2125
+ -webkit-print-color-adjust: exact;
2126
+ print-color-adjust: exact;
2127
+ color: ${cover.textColor || "#0F172A"};
2128
+ }
2129
+ .markforge-cover.cover-modern {
2130
+ border-top: 8px solid #0D998D;
2131
+ }
2132
+ .markforge-cover.cover-corporate-split {
2133
+ border-left: 12px solid #0D998D;
2134
+ }
2135
+ .markforge-cover.cover-card {
2136
+ background: #F8FAFC;
2137
+ }
2138
+ .cover-top {
2139
+ display: flex;
2140
+ justify-content: space-between;
2141
+ align-items: flex-start;
2142
+ width: 100%;
2143
+ }
2144
+ .cover-badge {
2145
+ display: inline-block;
2146
+ padding: 0.35rem 0.85rem;
2147
+ font-size: 0.78rem;
2148
+ font-weight: 700;
2149
+ letter-spacing: 0.08em;
2150
+ text-transform: uppercase;
2151
+ background-color: #ECFDFD;
2152
+ color: #0D998D;
2153
+ border-radius: 4px;
2154
+ border: 1px solid #33CDCF;
2155
+ }
2156
+ .cover-body {
2157
+ margin: auto 0;
2158
+ }
2159
+ .cover-title {
2160
+ font-size: 2.8rem;
2161
+ font-weight: 800;
2162
+ line-height: 1.15;
2163
+ margin: 0 0 1rem 0;
2164
+ color: inherit;
2165
+ }
2166
+ .cover-subtitle {
2167
+ font-size: 1.35rem;
2168
+ font-weight: 400;
2169
+ color: #64748B;
2170
+ margin: 0 0 2rem 0;
2171
+ line-height: 1.4;
2172
+ }
2173
+ .cover-meta {
2174
+ display: flex;
2175
+ flex-direction: column;
2176
+ gap: 0.5rem;
2177
+ border-top: 1.5px solid #E2E8F0;
2178
+ padding-top: 1.5rem;
2179
+ max-width: 480px;
2180
+ }
2181
+ .cover-meta-item {
2182
+ font-size: 0.92rem;
2183
+ display: flex;
2184
+ gap: 0.75rem;
2185
+ }
2186
+ .cover-meta-label {
2187
+ font-weight: 600;
2188
+ color: #64748B;
2189
+ min-width: 110px;
2190
+ }
2191
+ .cover-meta-value {
2192
+ font-weight: 500;
2193
+ color: #0F172A;
2194
+ }
2195
+ .cover-bottom {
2196
+ display: flex;
2197
+ justify-content: space-between;
2198
+ align-items: flex-end;
2199
+ font-size: 0.82rem;
2200
+ color: #94A3B8;
1764
2201
  }
1765
2202
  @media print {
1766
- .document-watermark {
1767
- position: fixed;
1768
- top: 0;
1769
- left: 0;
1770
- width: 100vw;
2203
+ .markforge-cover {
2204
+ page-break-after: always;
2205
+ break-after: page;
1771
2206
  height: 100vh;
2207
+ min-height: 100vh;
2208
+ max-height: 100vh;
2209
+ box-sizing: border-box;
2210
+ overflow: hidden;
2211
+ margin: 0;
1772
2212
  -webkit-print-color-adjust: exact;
1773
2213
  print-color-adjust: exact;
1774
2214
  }
1775
2215
  }
1776
2216
  `;
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) {}
1809
- })();
1810
- </script>
2217
+ const html = ` <section class="markforge-cover cover-${cover.preset}">
2218
+ <div class="cover-top">
2219
+ ${logoHtml}
2220
+ ${badgeHtml}
2221
+ </div>
2222
+ <div class="cover-body">
2223
+ ${titleHtml}
2224
+ ${subtitleHtml}
2225
+ ${metaHtml}
2226
+ </div>
2227
+ <div class="cover-bottom">
2228
+ ${footerHtml}
2229
+ </div>
2230
+ </section>
1811
2231
  `;
2232
+ return { html, css };
2233
+ }
2234
+ async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
2235
+ let logoHtml = "";
2236
+ if (backCover.logo) {
2237
+ const resolved = await resolveImage(backCover.logo, baseDir);
2238
+ const src = resolved ? resolved.dataUri : backCover.logo;
2239
+ const widthStyle = backCover.logoWidth ? `style="width: ${typeof backCover.logoWidth === "number" ? `${backCover.logoWidth}px` : backCover.logoWidth}; max-width: 100%;"` : `style="max-width: 160px; height: auto;"`;
2240
+ logoHtml = `<div class="back-logo"><img src="${src}" alt="Brand Logo" ${widthStyle} /></div>`;
1812
2241
  }
1813
- let signaturesHtml = "";
1814
- let signaturesCss = "";
1815
- if (resolved.signatures && resolved.signatures.items.length > 0) {
1816
- const sig = resolved.signatures;
1817
- 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
- signaturesCss = `
1823
- .markforge-signatures {
1824
- margin-top: ${sig.spacingBefore};
1825
- display: grid;
1826
- grid-template-columns: ${numItems === 1 ? sig.align === "left" ? "minmax(200px, 280px) 1fr" : sig.align === "center" ? "1fr minmax(200px, 280px) 1fr" : "1fr minmax(200px, 280px)" : `repeat(${numItems}, minmax(0, 1fr))`};
1827
- gap: 2rem;
1828
- page-break-inside: avoid;
1829
- break-inside: avoid;
2242
+ const badgeHtml = backCover.badge ? `<div class="back-badge" style="${backCover.badgeColor ? `background-color: ${backCover.badgeColor};` : ""}${backCover.badgeTextColor ? `color: ${backCover.badgeTextColor};` : ""}">${escapeHtml(backCover.badge)}</div>` : "";
2243
+ const titleHtml = `<h1 class="back-title">${escapeHtml(backCover.title)}</h1>`;
2244
+ const subtitleHtml = backCover.subtitle ? `<div class="back-subtitle">${escapeHtml(backCover.subtitle)}</div>` : "";
2245
+ const contactItems = [];
2246
+ 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>`);
2247
+ 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>`);
2248
+ 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>`);
2249
+ 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>`);
2250
+ 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>`);
2251
+ if (backCover.social) {
2252
+ for (const [network, url] of Object.entries(backCover.social)) {
2253
+ if (url) {
2254
+ 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>`);
2255
+ }
2256
+ }
1830
2257
  }
1831
- .markforge-signature-card {
1832
- ${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
1833
- ${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
2258
+ const contactHtml = contactItems.length > 0 ? `<div class="back-contact-grid">${contactItems.join("\n")}</div>` : "";
2259
+ const copyrightHtml = backCover.copyright ? `<div class="back-copyright">${escapeHtml(backCover.copyright)}</div>` : "";
2260
+ const isDark = backCover.preset === "corporate";
2261
+ const css = `
2262
+ .markforge-back-cover {
2263
+ min-height: 100vh;
2264
+ box-sizing: border-box;
2265
+ display: flex;
2266
+ flex-direction: column;
2267
+ justify-content: space-between;
2268
+ padding: 4rem 3.5rem;
2269
+ page-break-before: always;
2270
+ break-before: page;
2271
+ position: relative;
2272
+ z-index: 2;
2273
+ background: ${backCover.bgGradient || (isDark ? "#0F172A" : "#FFFFFF")};
2274
+ color: ${backCover.textColor || (isDark ? "#F8FAFC" : "#0F172A")};
2275
+ }
2276
+ .markforge-back-cover.back-modern {
2277
+ border-bottom: 8px solid #0D998D;
2278
+ }
2279
+ .markforge-back-cover.back-corporate {
2280
+ border-left: 12px solid #33CDCF;
2281
+ }
2282
+ .markforge-back-cover.back-card {
2283
+ background: #F8FAFC;
2284
+ }
2285
+ .back-top {
2286
+ display: flex;
2287
+ justify-content: space-between;
2288
+ align-items: flex-start;
2289
+ width: 100%;
2290
+ }
2291
+ .back-badge {
2292
+ display: inline-block;
2293
+ padding: 0.35rem 0.85rem;
2294
+ font-size: 0.78rem;
2295
+ font-weight: 700;
2296
+ letter-spacing: 0.08em;
2297
+ text-transform: uppercase;
2298
+ background-color: #ECFDFD;
2299
+ color: #0D998D;
2300
+ border-radius: 4px;
2301
+ border: 1px solid #33CDCF;
2302
+ }
2303
+ .back-body {
2304
+ margin: auto 0;
2305
+ }
2306
+ .back-title {
2307
+ font-size: 2.6rem;
2308
+ font-weight: 800;
2309
+ line-height: 1.15;
2310
+ margin: 0 0 0.75rem 0;
2311
+ color: inherit;
2312
+ }
2313
+ .back-subtitle {
2314
+ font-size: 1.25rem;
2315
+ font-weight: 400;
2316
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2317
+ margin: 0 0 2rem 0;
2318
+ line-height: 1.4;
2319
+ }
2320
+ .back-contact-grid {
2321
+ display: flex;
2322
+ flex-direction: column;
2323
+ gap: 0.6rem;
2324
+ border-top: 1.5px solid ${isDark ? "#334155" : "#E2E8F0"};
2325
+ padding-top: 1.5rem;
2326
+ max-width: 540px;
2327
+ }
2328
+ .back-contact-item {
2329
+ font-size: 0.92rem;
2330
+ display: flex;
2331
+ gap: 0.75rem;
2332
+ }
2333
+ .back-contact-label {
2334
+ font-weight: 600;
2335
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2336
+ min-width: 110px;
2337
+ }
2338
+ .back-contact-link {
2339
+ color: #0D998D;
2340
+ text-decoration: none;
2341
+ font-weight: 600;
2342
+ }
2343
+ .back-contact-link:hover {
2344
+ text-decoration: underline;
2345
+ }
2346
+ .back-copyright {
2347
+ font-size: 0.82rem;
2348
+ color: ${isDark ? "#64748B" : "#94A3B8"};
2349
+ border-top: 1px solid ${isDark ? "#1E293B" : "#F1F5F9"};
2350
+ padding-top: 1rem;
2351
+ margin-top: 2rem;
2352
+ }
2353
+ @media print {
2354
+ .markforge-back-cover {
2355
+ page: back-cover-page;
2356
+ page-break-before: always;
2357
+ break-before: page;
2358
+ page-break-after: avoid;
2359
+ break-after: avoid;
2360
+ min-height: 100vh;
2361
+ height: 100vh;
2362
+ max-height: 100vh;
2363
+ margin: 0;
2364
+ box-sizing: border-box;
2365
+ overflow: hidden;
2366
+ -webkit-print-color-adjust: exact;
2367
+ print-color-adjust: exact;
2368
+ }
2369
+ }
2370
+ `;
2371
+ const html = ` <section class="markforge-back-cover back-${backCover.preset}">
2372
+ <div class="back-top">
2373
+ ${logoHtml}
2374
+ ${badgeHtml}
2375
+ </div>
2376
+ <div class="back-body">
2377
+ ${titleHtml}
2378
+ ${subtitleHtml}
2379
+ ${contactHtml}
2380
+ </div>
2381
+ ${copyrightHtml}
2382
+ </section>
2383
+ `;
2384
+ return { html, css };
2385
+ }
2386
+ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2387
+ var _a;
2388
+ const resolved = resolveDocumentConfig(doc.metadata, config);
2389
+ const baseThemeCss = generateThemeCss(resolved.theme);
2390
+ let customCss = "";
2391
+ for (const cssPath of resolved.css) {
2392
+ const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
2393
+ if (fs3.existsSync(fullCssPath)) {
2394
+ customCss += `
2395
+ /* Custom CSS: ${cssPath} */
2396
+ ` + fs3.readFileSync(fullCssPath, "utf-8");
2397
+ }
2398
+ }
2399
+ const inlinedCss = doc.inlinedStyles.join("\n");
2400
+ const extraCss = `
2401
+ .markforge-columns {
2402
+ display: grid;
2403
+ grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
2404
+ gap: var(--col-gap, 1.5rem);
2405
+ margin: 1.5rem 0;
2406
+ }
2407
+ .markforge-col {
2408
+ min-width: 0;
2409
+ }
2410
+ .markforge-fnref {
2411
+ text-decoration: none;
2412
+ font-size: 0.8em;
2413
+ vertical-align: super;
2414
+ color: #0D998D;
2415
+ font-weight: 700;
2416
+ }
2417
+ .markforge-footnotes {
2418
+ margin-top: 3rem;
2419
+ padding-top: 1rem;
2420
+ font-size: 0.88rem;
2421
+ color: #64748B;
2422
+ }
2423
+ .markforge-footnotes hr {
2424
+ border: 0;
2425
+ border-top: 1px solid #E2E8F0;
2426
+ margin-bottom: 1rem;
2427
+ }
2428
+ .markforge-fn-return {
2429
+ text-decoration: none;
2430
+ color: #0D998D;
2431
+ }
2432
+ .math-block {
2433
+ margin: 1.5rem 0;
2434
+ text-align: center;
2435
+ overflow-x: auto;
2436
+ }
2437
+ `;
2438
+ let coverHtml = "";
2439
+ let coverCss = "";
2440
+ if (resolved.coverPage && resolved.coverPage.enabled) {
2441
+ const coverRes = await renderCoverPageHtml(resolved.coverPage, baseDir);
2442
+ coverHtml = coverRes.html;
2443
+ coverCss = coverRes.css;
2444
+ }
2445
+ let backHtml = "";
2446
+ let backCss = "";
2447
+ if (resolved.backCover && resolved.backCover.enabled) {
2448
+ const backRes = await renderBackCoverHtml(resolved.backCover, baseDir);
2449
+ backHtml = backRes.html;
2450
+ backCss = backRes.css;
2451
+ }
2452
+ let bodyHtml = "";
2453
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2454
+ bodyHtml += ` <header class="document-header">
2455
+ `;
2456
+ bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
2457
+ `;
2458
+ if (resolved.subtitle) {
2459
+ bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
2460
+ `;
2461
+ }
2462
+ if (resolved.author || resolved.date || resolved.version) {
2463
+ bodyHtml += ` <div class="document-meta">
2464
+ `;
2465
+ if (resolved.author) {
2466
+ bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
2467
+ `;
2468
+ }
2469
+ if (resolved.version) {
2470
+ bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
2471
+ `;
2472
+ }
2473
+ if (resolved.date) {
2474
+ bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
2475
+ `;
2476
+ }
2477
+ bodyHtml += ` </div>
2478
+ `;
2479
+ }
2480
+ bodyHtml += ` </header>
2481
+ `;
2482
+ }
2483
+ if (resolved.toc && doc.tocEntries.length > 0) {
2484
+ bodyHtml += ` <nav class="table-of-contents">
2485
+ `;
2486
+ bodyHtml += ` <h2>Table of Contents</h2>
2487
+ <ul>
2488
+ `;
2489
+ for (const entry of doc.tocEntries) {
2490
+ const indent = " ".repeat(entry.level);
2491
+ bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
2492
+ `;
2493
+ }
2494
+ bodyHtml += ` </ul>
2495
+ </nav>
2496
+ `;
2497
+ }
2498
+ bodyHtml += await renderNodesToHtml(doc.nodes, resolved, baseDir);
2499
+ let footnotesHtml = "";
2500
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
2501
+ let fnListHtml = "";
2502
+ for (const def of doc.footnoteDefs) {
2503
+ const defInner = await renderInlinesToHtml(def.inlines, baseDir);
2504
+ fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">&#8617;</a></li>
2505
+ `;
2506
+ }
2507
+ footnotesHtml = `
2508
+ <footer class="markforge-footnotes">
2509
+ <hr />
2510
+ <ol>
2511
+ ${fnListHtml} </ol>
2512
+ </footer>
2513
+ `;
2514
+ }
2515
+ let watermarkCss = "";
2516
+ let watermarkHtml = "";
2517
+ if (resolved.watermark) {
2518
+ const wm = resolved.watermark;
2519
+ watermarkCss = `
2520
+ .document-watermark {
2521
+ position: fixed;
2522
+ top: 0;
2523
+ left: 0;
2524
+ right: 0;
2525
+ bottom: 0;
2526
+ width: 100%;
2527
+ height: 100%;
2528
+ pointer-events: none;
2529
+ z-index: 0;
2530
+ user-select: none;
2531
+ -webkit-user-select: none;
2532
+ -webkit-print-color-adjust: exact;
2533
+ print-color-adjust: exact;
2534
+ }
2535
+ .document-container {
2536
+ position: relative;
2537
+ z-index: 1;
2538
+ }
2539
+ @media print {
2540
+ .document-watermark {
2541
+ position: fixed;
2542
+ top: 0;
2543
+ left: 0;
2544
+ right: 0;
2545
+ bottom: 0;
2546
+ width: 100vw;
2547
+ height: 100vh;
2548
+ pointer-events: none;
2549
+ z-index: 0;
2550
+ -webkit-print-color-adjust: exact;
2551
+ print-color-adjust: exact;
2552
+ }
2553
+ }
2554
+ `;
2555
+ watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
2556
+ <script>
2557
+ (function() {
2558
+ try {
2559
+ var canvas = document.createElement('canvas');
2560
+ var dpr = 2;
2561
+ var width = 1200;
2562
+ var height = 1600;
2563
+ canvas.width = width * dpr;
2564
+ canvas.height = height * dpr;
2565
+ var ctx = canvas.getContext('2d');
2566
+ if (ctx) {
2567
+ ctx.scale(dpr, dpr);
2568
+ ctx.translate(width / 2, height / 2);
2569
+ ctx.rotate((-Math.abs(${wm.rotate || 45}) * Math.PI) / 180);
2570
+ ctx.textAlign = 'center';
2571
+ ctx.textBaseline = 'middle';
2572
+ ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
2573
+ ctx.fillStyle = '${wm.color}';
2574
+ ctx.globalAlpha = ${wm.opacity};
2575
+ try { ctx.letterSpacing = '0.15em'; } catch(e) {}
2576
+ ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
2577
+ var dataUrl = canvas.toDataURL('image/png');
2578
+ var wmEl = document.getElementById('markforge-watermark');
2579
+ if (wmEl) {
2580
+ wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
2581
+ wmEl.style.backgroundRepeat = 'no-repeat';
2582
+ wmEl.style.backgroundPosition = 'center center';
2583
+ wmEl.style.backgroundSize = 'contain';
2584
+ }
2585
+ }
2586
+ } catch(err) {}
2587
+ })();
2588
+ </script>
2589
+ `;
2590
+ }
2591
+ let signaturesHtml = "";
2592
+ let signaturesCss = "";
2593
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
2594
+ const sig = resolved.signatures;
2595
+ const numItems = sig.items.length;
2596
+ signaturesCss = `
2597
+ .markforge-signatures {
2598
+ margin-top: ${sig.spacingBefore};
2599
+ display: grid;
2600
+ grid-template-columns: ${numItems === 1 ? sig.align === "left" ? "minmax(200px, 280px) 1fr" : sig.align === "center" ? "1fr minmax(200px, 280px) 1fr" : "1fr minmax(200px, 280px)" : `repeat(${numItems}, minmax(0, 1fr))`};
2601
+ gap: 2rem;
2602
+ page-break-inside: avoid;
2603
+ break-inside: avoid;
2604
+ }
2605
+ .markforge-signature-card {
2606
+ ${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
2607
+ ${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
1834
2608
  display: flex;
1835
2609
  flex-direction: column;
1836
2610
  ${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
@@ -1930,6 +2704,10 @@ ${itemCards}
1930
2704
  <style>
1931
2705
  ${THEME_COMPONENTS}
1932
2706
  ${baseThemeCss}
2707
+ ${KATEX_INLINE_CSS}
2708
+ ${extraCss}
2709
+ ${coverCss}
2710
+ ${backCss}
1933
2711
  ${customCss}
1934
2712
  ${inlinedCss}
1935
2713
  ${watermarkCss}
@@ -1937,14 +2715,95 @@ ${signaturesCss}
1937
2715
  </style>
1938
2716
  </head>
1939
2717
  <body>
1940
- ${watermarkHtml} <div class="document-container">
1941
- ${bodyHtml}${signaturesHtml} </div>
2718
+ ${watermarkHtml}${coverHtml} <div class="document-container">
2719
+ ${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
1942
2720
  ${mermaidScript}
1943
- </body>
2721
+ ${backHtml}</body>
1944
2722
  </html>`;
1945
2723
  }
1946
2724
 
1947
2725
  // src/core/pdf/pdfBuilder.ts
2726
+ function escapeXml(str) {
2727
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2728
+ }
2729
+ function generateWatermarkPngBuffer(chromePath, wm) {
2730
+ const tmpHtml = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
2731
+ const tmpPng = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
2732
+ try {
2733
+ const text = escapeXml(wm.text.toUpperCase());
2734
+ const fontSize = (wm.fontSize || 52) * 1.5;
2735
+ const color = wm.color || "#E11D48";
2736
+ const opacity = wm.opacity !== void 0 ? wm.opacity : 0.12;
2737
+ const rotate = wm.rotate !== void 0 ? wm.rotate : -45;
2738
+ const html = `<!DOCTYPE html>
2739
+ <html>
2740
+ <head>
2741
+ <meta charset="utf-8">
2742
+ <style>
2743
+ html, body {
2744
+ margin: 0;
2745
+ padding: 0;
2746
+ width: 1200px;
2747
+ height: 1600px;
2748
+ background: transparent;
2749
+ overflow: hidden;
2750
+ }
2751
+ .wm-box {
2752
+ width: 1200px;
2753
+ height: 1600px;
2754
+ display: flex;
2755
+ align-items: center;
2756
+ justify-content: center;
2757
+ transform: rotate(${rotate}deg);
2758
+ }
2759
+ .wm-text {
2760
+ font-family: system-ui, -apple-system, sans-serif;
2761
+ font-weight: 900;
2762
+ font-size: ${fontSize}px;
2763
+ color: ${color};
2764
+ opacity: ${opacity};
2765
+ letter-spacing: 0.15em;
2766
+ text-transform: uppercase;
2767
+ white-space: nowrap;
2768
+ }
2769
+ </style>
2770
+ </head>
2771
+ <body>
2772
+ <div class="wm-box"><span class="wm-text">${text}</span></div>
2773
+ </body>
2774
+ </html>`;
2775
+ fs4.writeFileSync(tmpHtml, html, "utf8");
2776
+ const fileUrl = (0, import_node_url2.pathToFileURL)(tmpHtml).href;
2777
+ const isWin = process.platform === "win32";
2778
+ (0, import_node_child_process.spawnSync)(
2779
+ chromePath,
2780
+ [
2781
+ "--headless=new",
2782
+ "--disable-gpu",
2783
+ "--disable-sync",
2784
+ "--disable-extensions",
2785
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2786
+ `--screenshot=${tmpPng}`,
2787
+ "--window-size=1200,1600",
2788
+ "--default-background-color=00000000",
2789
+ fileUrl
2790
+ ],
2791
+ { timeout: 15e3, windowsHide: true }
2792
+ );
2793
+ if (fs4.existsSync(tmpPng) && fs4.statSync(tmpPng).size > 0) {
2794
+ return fs4.readFileSync(tmpPng);
2795
+ }
2796
+ return null;
2797
+ } catch {
2798
+ return null;
2799
+ } finally {
2800
+ try {
2801
+ if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
2802
+ if (fs4.existsSync(tmpPng)) fs4.unlinkSync(tmpPng);
2803
+ } catch {
2804
+ }
2805
+ }
2806
+ }
1948
2807
  function findChromeExecutable() {
1949
2808
  if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1950
2809
  return process.env.CHROME_PATH;
@@ -2009,7 +2868,7 @@ function findChromeExecutable() {
2009
2868
  return null;
2010
2869
  }
2011
2870
  function injectPagedMediaStyles(html, config, metadata) {
2012
- var _a, _b, _c, _d, _e, _f;
2871
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2013
2872
  const resolved = resolveDocumentConfig(metadata || {}, config);
2014
2873
  const size = resolved.paperSize;
2015
2874
  const orientation = resolved.orientation;
@@ -2053,6 +2912,38 @@ function injectPagedMediaStyles(html, config, metadata) {
2053
2912
  ${fontStyle}
2054
2913
  }`;
2055
2914
  };
2915
+ const coverPageCss = ((_a = resolved.coverPage) == null ? void 0 : _a.enabled) ? `
2916
+ @page :first {
2917
+ margin-top: 0;
2918
+ margin-bottom: 0;
2919
+ margin-left: 0;
2920
+ margin-right: 0;
2921
+ background-image: none !important;
2922
+ @top-left { content: none; }
2923
+ @top-center { content: none; }
2924
+ @top-right { content: none; }
2925
+ @bottom-left { content: none; }
2926
+ @bottom-center { content: none; }
2927
+ @bottom-right { content: none; }
2928
+ }` : "";
2929
+ const backCoverCss = ((_b = resolved.backCover) == null ? void 0 : _b.enabled) ? `
2930
+ @page back-cover-page {
2931
+ size: ${size} ${orientation};
2932
+ margin: 0;
2933
+ background-image: none !important;
2934
+ @top-left { content: none; }
2935
+ @top-center { content: none; }
2936
+ @top-right { content: none; }
2937
+ @bottom-left { content: none; }
2938
+ @bottom-center { content: none; }
2939
+ @bottom-right { content: none; }
2940
+ }
2941
+ .markforge-back-cover {
2942
+ page: back-cover-page;
2943
+ min-height: 100vh;
2944
+ height: 100vh;
2945
+ box-sizing: border-box;
2946
+ }` : "";
2056
2947
  const pagedCss = `
2057
2948
  @page {
2058
2949
  size: ${size} ${orientation};
@@ -2060,15 +2951,18 @@ function injectPagedMediaStyles(html, config, metadata) {
2060
2951
  margin-bottom: ${bottom};
2061
2952
  margin-left: ${left};
2062
2953
  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)}
2954
+ ${buildZoneCss("top-left", (_c = resolved.header) == null ? void 0 : _c.left)}
2955
+ ${buildZoneCss("top-center", (_d = resolved.header) == null ? void 0 : _d.center)}
2956
+ ${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
2957
+ ${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
2958
+ ${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
2959
+ ${buildZoneCss("bottom-right", (_h = resolved.footer) == null ? void 0 : _h.right, true)}
2069
2960
  }
2961
+ ${coverPageCss}
2962
+ ${backCoverCss}
2070
2963
  @media print {
2071
2964
  body { padding: 0; }
2965
+ .document-watermark { display: none !important; }
2072
2966
  h1, h2, h3, pre, table, blockquote, .callout {
2073
2967
  break-inside: avoid;
2074
2968
  }
@@ -2116,6 +3010,7 @@ startxref
2116
3010
  return Buffer.from(pdfBody, "utf-8");
2117
3011
  }
2118
3012
  async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
3013
+ var _a, _b, _c;
2119
3014
  const baseHtml = await buildHtmlDocument(doc, config, baseDir);
2120
3015
  const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
2121
3016
  const chromePath = findChromeExecutable();
@@ -2125,6 +3020,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2125
3020
  const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
2126
3021
  const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
2127
3022
  const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
3023
+ const isWin = process.platform === "win32";
2128
3024
  const isolatedFlags = [
2129
3025
  `--user-data-dir=${tmpProfile}`,
2130
3026
  "--no-first-run",
@@ -2135,7 +3031,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2135
3031
  "--disable-default-apps",
2136
3032
  "--disable-extensions",
2137
3033
  "--disable-domain-reliability",
2138
- "--disable-client-side-phishing-detection",
2139
3034
  "--disable-breakpad",
2140
3035
  "--disable-component-extensions-with-background-pages",
2141
3036
  "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
@@ -2144,12 +3039,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2144
3039
  "--mute-audio",
2145
3040
  "--no-service-autorun",
2146
3041
  "--disable-gpu",
2147
- "--no-sandbox",
2148
- "--disable-setuid-sandbox",
2149
- "--allow-file-access-from-files",
2150
- "--disable-web-security",
3042
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2151
3043
  "--force-color-profile=srgb",
2152
- "--no-pdf-header-footer"
3044
+ "--no-pdf-header-footer",
3045
+ "--window-size=1200,1600"
2153
3046
  ];
2154
3047
  try {
2155
3048
  fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
@@ -2164,7 +3057,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2164
3057
  `--print-to-pdf=${tmpPdf}`,
2165
3058
  fileUrl
2166
3059
  ],
2167
- { timeout: 3e4 }
3060
+ { timeout: 3e4, windowsHide: true }
2168
3061
  );
2169
3062
  if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
2170
3063
  res = (0, import_node_child_process.spawnSync)(
@@ -2175,12 +3068,78 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2175
3068
  `--print-to-pdf=${tmpPdf}`,
2176
3069
  fileUrl
2177
3070
  ],
2178
- { timeout: 3e4 }
3071
+ { timeout: 3e4, windowsHide: true }
2179
3072
  );
2180
3073
  }
2181
3074
  if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
2182
3075
  const pdfBuffer = fs4.readFileSync(tmpPdf);
2183
- return pdfBuffer;
3076
+ try {
3077
+ const pdfDoc = await import_pdf_lib.PDFDocument.load(pdfBuffer);
3078
+ const resolved = resolveDocumentConfig(doc.metadata, config);
3079
+ if (resolved.title) pdfDoc.setTitle(resolved.title);
3080
+ if (resolved.author) pdfDoc.setAuthor(resolved.author);
3081
+ if (resolved.subtitle) pdfDoc.setSubject(resolved.subtitle);
3082
+ pdfDoc.setCreator("MarkForge Enterprise Document Generator");
3083
+ pdfDoc.setProducer("MarkForge (by Ma'sum)");
3084
+ pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
3085
+ if (((_a = resolved.backCover) == null ? void 0 : _a.enabled) && pdfDoc.getPageCount() > 2) {
3086
+ pdfDoc.removePage(pdfDoc.getPageCount() - 1);
3087
+ }
3088
+ if (resolved.watermark) {
3089
+ const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
3090
+ if (wmPng) {
3091
+ const embeddedPng = await pdfDoc.embedPng(wmPng);
3092
+ const pages = pdfDoc.getPages();
3093
+ const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
3094
+ const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? pages.length - 1 : pages.length;
3095
+ for (let i = startPageIndex; i < endPageIndex; i++) {
3096
+ const page = pages[i];
3097
+ const { width, height } = page.getSize();
3098
+ page.drawImage(embeddedPng, {
3099
+ x: 0,
3100
+ y: 0,
3101
+ width,
3102
+ height
3103
+ });
3104
+ }
3105
+ }
3106
+ }
3107
+ const savedBytes = await pdfDoc.save();
3108
+ let finalBuffer = Buffer.from(savedBytes);
3109
+ if (resolved.security) {
3110
+ const sec = resolved.security;
3111
+ const hasUserPassword = typeof sec.userPassword === "string" && sec.userPassword.length > 0;
3112
+ const hasOwnerPassword = typeof sec.ownerPassword === "string" && sec.ownerPassword.length > 0;
3113
+ if (hasUserPassword || hasOwnerPassword) {
3114
+ try {
3115
+ const userPass = sec.userPassword ?? "";
3116
+ const ownerPass = sec.ownerPassword ?? userPass;
3117
+ const perms = sec.permissions;
3118
+ const encryptedBytes = await (0, import_pdf_encrypt.encryptPDF)(
3119
+ new Uint8Array(finalBuffer),
3120
+ userPass,
3121
+ {
3122
+ ownerPassword: ownerPass,
3123
+ algorithm: "AES-256",
3124
+ allowPrinting: (perms == null ? void 0 : perms.printing) !== "none",
3125
+ allowHighQualityPrint: (perms == null ? void 0 : perms.printing) === "highResolution",
3126
+ allowModifying: (perms == null ? void 0 : perms.modifying) ?? true,
3127
+ allowCopying: (perms == null ? void 0 : perms.copying) ?? true,
3128
+ allowAnnotating: (perms == null ? void 0 : perms.annotating) ?? true,
3129
+ allowFillingForms: (perms == null ? void 0 : perms.fillingForms) ?? true,
3130
+ allowExtraction: (perms == null ? void 0 : perms.contentAccessibility) ?? true,
3131
+ allowAssembly: (perms == null ? void 0 : perms.documentAssembly) ?? true
3132
+ }
3133
+ );
3134
+ finalBuffer = Buffer.from(encryptedBytes);
3135
+ } catch {
3136
+ }
3137
+ }
3138
+ }
3139
+ return finalBuffer;
3140
+ } catch {
3141
+ return pdfBuffer;
3142
+ }
2184
3143
  }
2185
3144
  } catch {
2186
3145
  } finally {
@@ -2435,6 +3394,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2435
3394
  );
2436
3395
  continue;
2437
3396
  }
3397
+ if (span.type === "mathInline") {
3398
+ runs.push(
3399
+ new import_docx.TextRun({
3400
+ text: span.content,
3401
+ font: "Cambria Math",
3402
+ italics: true,
3403
+ size: options.size,
3404
+ color: options.color || "0F172A"
3405
+ })
3406
+ );
3407
+ continue;
3408
+ }
3409
+ if (span.type === "footnoteRef") {
3410
+ runs.push(
3411
+ new import_docx.TextRun({
3412
+ text: `[${span.content}]`,
3413
+ superScript: true,
3414
+ color: "009DA0",
3415
+ bold: true
3416
+ })
3417
+ );
3418
+ continue;
3419
+ }
2438
3420
  runs.push(
2439
3421
  new import_docx.TextRun({
2440
3422
  text: span.content,
@@ -2449,7 +3431,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2449
3431
  return runs;
2450
3432
  }
2451
3433
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2452
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3434
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2453
3435
  const resolved = resolveDocumentConfig(doc.metadata, config);
2454
3436
  const docElements = [];
2455
3437
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -2460,7 +3442,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2460
3442
  const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2461
3443
  const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2462
3444
  const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2463
- if (resolved.title) {
3445
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2464
3446
  docElements.push(
2465
3447
  new import_docx.Paragraph({
2466
3448
  children: [
@@ -2876,7 +3858,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2876
3858
  }
2877
3859
  if (node.type === "table" && node.children) {
2878
3860
  const tableRows = [];
2879
- const numCols = ((_b = (_a = node.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) || 1;
3861
+ const numCols = ((_c = (_b = node.children[0]) == null ? void 0 : _b.children) == null ? void 0 : _c.length) || 1;
2880
3862
  const colWidth = Math.floor(9e3 / numCols);
2881
3863
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2882
3864
  const rowNode = node.children[rowIdx];
@@ -2886,7 +3868,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2886
3868
  if (rowNode.children) {
2887
3869
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2888
3870
  const cellNode = rowNode.children[colIdx];
2889
- const align = (_c = node.align) == null ? void 0 : _c[colIdx];
3871
+ const align = (_d = node.align) == null ? void 0 : _d[colIdx];
2890
3872
  let alignment = import_docx.AlignmentType.LEFT;
2891
3873
  if (align === "center") alignment = import_docx.AlignmentType.CENTER;
2892
3874
  if (align === "right") alignment = import_docx.AlignmentType.RIGHT;
@@ -3025,12 +4007,119 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3025
4007
  }
3026
4008
  continue;
3027
4009
  }
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,
4010
+ if (node.type === "mathBlock") {
4011
+ docElements.push(
4012
+ new import_docx.Paragraph({
4013
+ alignment: import_docx.AlignmentType.CENTER,
4014
+ children: [
4015
+ new import_docx.TextRun({
4016
+ text: node.text || "",
4017
+ font: "Cambria Math",
4018
+ italics: true,
4019
+ size: 24,
4020
+ // 12pt
4021
+ color: textHex
4022
+ })
4023
+ ],
4024
+ spacing: { before: 180, after: 180 },
4025
+ shading: { fill: cardBgHex, type: import_docx.ShadingType.CLEAR },
4026
+ border: {
4027
+ top: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex },
4028
+ bottom: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex },
4029
+ left: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex },
4030
+ right: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex }
4031
+ }
4032
+ })
4033
+ );
4034
+ continue;
4035
+ }
4036
+ if (node.type === "columns") {
4037
+ const cols = node.columnsCount || 2;
4038
+ const contentWidth = Math.max(
4039
+ 1e3,
4040
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4041
+ );
4042
+ const cellWidthDxa = Math.floor(contentWidth / cols);
4043
+ const cells = [];
4044
+ for (const col of node.children || []) {
4045
+ const colParagraphs = [];
4046
+ for (const childNode of col.children || []) {
4047
+ if (childNode.type === "heading") {
4048
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, bold: true, size: 24, color: primaryDarkHex });
4049
+ colParagraphs.push(new import_docx.Paragraph({ children: runs, spacing: { before: 120, after: 60 } }));
4050
+ } else if (childNode.type === "paragraph") {
4051
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
4052
+ colParagraphs.push(new import_docx.Paragraph({ children: runs, spacing: { after: 100 } }));
4053
+ } else if (childNode.type === "list" && childNode.children) {
4054
+ for (const item of childNode.children) {
4055
+ const runs = await convertInlinesToTextRuns(item.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
4056
+ colParagraphs.push(new import_docx.Paragraph({ children: [new import_docx.TextRun({ text: "\u2022 ", font: defaultFont, color: primaryHex }), ...runs], spacing: { after: 40 } }));
4057
+ }
4058
+ }
4059
+ }
4060
+ if (colParagraphs.length === 0) colParagraphs.push(new import_docx.Paragraph({}));
4061
+ cells.push(
4062
+ new import_docx.TableCell({
4063
+ width: { size: cellWidthDxa, type: import_docx.WidthType.DXA },
4064
+ borders: {
4065
+ top: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4066
+ bottom: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4067
+ left: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4068
+ right: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" }
4069
+ },
4070
+ margins: { top: 60, bottom: 60, left: 100, right: 100 },
4071
+ children: colParagraphs
4072
+ })
4073
+ );
4074
+ }
4075
+ docElements.push(
4076
+ new import_docx.Table({
4077
+ width: { size: 100, type: import_docx.WidthType.PERCENTAGE },
4078
+ rows: [new import_docx.TableRow({ children: cells })]
4079
+ })
4080
+ );
4081
+ docElements.push(new import_docx.Paragraph({ spacing: { after: 120 } }));
4082
+ continue;
4083
+ }
4084
+ }
4085
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
4086
+ docElements.push(
4087
+ new import_docx.Paragraph({
4088
+ border: {
4089
+ top: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex, space: 8 }
4090
+ },
4091
+ spacing: { before: 360, after: 120 }
4092
+ })
4093
+ );
4094
+ for (const def of doc.footnoteDefs) {
4095
+ const defRuns = await convertInlinesToTextRuns(def.inlines, baseDir, {
4096
+ font: defaultFont,
4097
+ size: 18,
4098
+ // 9pt
4099
+ color: textMutedHex
4100
+ });
4101
+ docElements.push(
4102
+ new import_docx.Paragraph({
4103
+ children: [
4104
+ new import_docx.TextRun({
4105
+ text: `[${def.id}] `,
4106
+ bold: true,
4107
+ color: primaryDarkHex,
4108
+ font: defaultFont,
4109
+ size: 18
4110
+ }),
4111
+ ...defRuns
4112
+ ],
4113
+ spacing: { after: 60 }
4114
+ })
4115
+ );
4116
+ }
4117
+ }
4118
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
4119
+ const sig = resolved.signatures;
4120
+ const numItems = sig.items.length;
4121
+ const contentWidth = Math.max(
4122
+ 1e3,
3034
4123
  resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
3035
4124
  );
3036
4125
  docElements.push(new import_docx.Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
@@ -3086,7 +4175,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3086
4175
  const centerPos = Math.round(contentWidthTwip / 2);
3087
4176
  const rightPos = contentWidthTwip;
3088
4177
  const headerRuns = [];
3089
- if ((_d = resolved.header) == null ? void 0 : _d.left) {
4178
+ if ((_e = resolved.header) == null ? void 0 : _e.left) {
3090
4179
  headerRuns.push(
3091
4180
  new import_docx.TextRun({
3092
4181
  text: resolved.header.left.text,
@@ -3099,7 +4188,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3099
4188
  );
3100
4189
  }
3101
4190
  headerRuns.push(new import_docx.TextRun({ text: " " }));
3102
- if ((_e = resolved.header) == null ? void 0 : _e.center) {
4191
+ if ((_f = resolved.header) == null ? void 0 : _f.center) {
3103
4192
  headerRuns.push(
3104
4193
  new import_docx.TextRun({
3105
4194
  text: resolved.header.center.text,
@@ -3112,7 +4201,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3112
4201
  );
3113
4202
  }
3114
4203
  headerRuns.push(new import_docx.TextRun({ text: " " }));
3115
- if ((_f = resolved.header) == null ? void 0 : _f.right) {
4204
+ if ((_g = resolved.header) == null ? void 0 : _g.right) {
3116
4205
  headerRuns.push(
3117
4206
  new import_docx.TextRun({
3118
4207
  text: resolved.header.right.text,
@@ -3151,7 +4240,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3151
4240
  ]
3152
4241
  }) : void 0;
3153
4242
  const footerRuns = [];
3154
- if ((_g = resolved.footer) == null ? void 0 : _g.left) {
4243
+ if ((_h = resolved.footer) == null ? void 0 : _h.left) {
3155
4244
  footerRuns.push(
3156
4245
  new import_docx.TextRun({
3157
4246
  text: resolved.footer.left.text,
@@ -3164,7 +4253,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3164
4253
  );
3165
4254
  }
3166
4255
  footerRuns.push(new import_docx.TextRun({ text: " " }));
3167
- if ((_h = resolved.footer) == null ? void 0 : _h.center) {
4256
+ if ((_i = resolved.footer) == null ? void 0 : _i.center) {
3168
4257
  footerRuns.push(
3169
4258
  new import_docx.TextRun({
3170
4259
  text: resolved.footer.center.text,
@@ -3177,7 +4266,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3177
4266
  );
3178
4267
  }
3179
4268
  footerRuns.push(new import_docx.TextRun({ text: " " }));
3180
- if ((_i = resolved.footer) == null ? void 0 : _i.right) {
4269
+ if ((_j = resolved.footer) == null ? void 0 : _j.right) {
3181
4270
  const rZone = resolved.footer.right;
3182
4271
  const rColor = rZone.color.replace("#", "");
3183
4272
  const rSize = (rZone.fontSize || 9) * 2;
@@ -3262,6 +4351,91 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3262
4351
  ]
3263
4352
  }) : void 0;
3264
4353
  const isLandscape = resolved.orientation === "landscape";
4354
+ const docSections = [];
4355
+ if (resolved.coverPage && resolved.coverPage.enabled) {
4356
+ const coverElements = await buildDocxCoverPageElements(
4357
+ resolved.coverPage,
4358
+ defaultFont,
4359
+ textHex,
4360
+ primaryHex,
4361
+ primaryDarkHex,
4362
+ textMutedHex,
4363
+ baseDir
4364
+ );
4365
+ docSections.push({
4366
+ properties: {
4367
+ page: {
4368
+ size: {
4369
+ width: resolved.paperDimensions.widthTwip,
4370
+ height: resolved.paperDimensions.heightTwip,
4371
+ orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
4372
+ },
4373
+ margin: {
4374
+ top: resolved.margins.topTwip,
4375
+ bottom: resolved.margins.bottomTwip,
4376
+ left: resolved.margins.leftTwip,
4377
+ right: resolved.margins.rightTwip
4378
+ }
4379
+ }
4380
+ },
4381
+ headers: void 0,
4382
+ footers: void 0,
4383
+ children: coverElements
4384
+ });
4385
+ }
4386
+ docSections.push({
4387
+ properties: {
4388
+ page: {
4389
+ size: {
4390
+ width: resolved.paperDimensions.widthTwip,
4391
+ height: resolved.paperDimensions.heightTwip,
4392
+ orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
4393
+ },
4394
+ margin: {
4395
+ top: resolved.margins.topTwip,
4396
+ bottom: resolved.margins.bottomTwip,
4397
+ left: resolved.margins.leftTwip,
4398
+ right: resolved.margins.rightTwip,
4399
+ header: 720,
4400
+ footer: 720
4401
+ }
4402
+ }
4403
+ },
4404
+ headers: docHeader ? { default: docHeader } : void 0,
4405
+ footers: docFooter ? { default: docFooter } : void 0,
4406
+ children: docElements
4407
+ });
4408
+ if (resolved.backCover && resolved.backCover.enabled) {
4409
+ const backElements = await buildDocxBackCoverElements(
4410
+ resolved.backCover,
4411
+ defaultFont,
4412
+ textHex,
4413
+ primaryHex,
4414
+ primaryDarkHex,
4415
+ textMutedHex,
4416
+ baseDir
4417
+ );
4418
+ docSections.push({
4419
+ properties: {
4420
+ page: {
4421
+ size: {
4422
+ width: resolved.paperDimensions.widthTwip,
4423
+ height: resolved.paperDimensions.heightTwip,
4424
+ orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
4425
+ },
4426
+ margin: {
4427
+ top: resolved.margins.topTwip,
4428
+ bottom: resolved.margins.bottomTwip,
4429
+ left: resolved.margins.leftTwip,
4430
+ right: resolved.margins.rightTwip
4431
+ }
4432
+ }
4433
+ },
4434
+ headers: void 0,
4435
+ footers: void 0,
4436
+ children: backElements
4437
+ });
4438
+ }
3265
4439
  const document = new import_docx.Document({
3266
4440
  styles: {
3267
4441
  default: {
@@ -3282,33 +4456,250 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3282
4456
  }
3283
4457
  }
3284
4458
  },
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
- ]
4459
+ sections: docSections
3309
4460
  });
3310
4461
  return await import_docx.Packer.toBuffer(document);
3311
4462
  }
4463
+ async function buildDocxBackCoverElements(backCover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4464
+ var _a;
4465
+ const elements = [];
4466
+ elements.push(new import_docx.Paragraph({ spacing: { before: 1800 } }));
4467
+ if (backCover.logo) {
4468
+ const resolvedLogo = await resolveImage(backCover.logo, baseDir);
4469
+ if (resolvedLogo) {
4470
+ const logoW = typeof backCover.logoWidth === "number" ? backCover.logoWidth : 140;
4471
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4472
+ elements.push(
4473
+ new import_docx.Paragraph({
4474
+ children: [
4475
+ new import_docx.ImageRun({
4476
+ data: resolvedLogo.buffer,
4477
+ transformation: {
4478
+ width: logoW,
4479
+ height: Math.round(logoW * 0.75)
4480
+ },
4481
+ type: logoType
4482
+ })
4483
+ ],
4484
+ spacing: { after: 240 }
4485
+ })
4486
+ );
4487
+ }
4488
+ }
4489
+ if (backCover.badge) {
4490
+ elements.push(
4491
+ new import_docx.Paragraph({
4492
+ children: [
4493
+ new import_docx.TextRun({
4494
+ text: `[ ${backCover.badge.toUpperCase()} ]`,
4495
+ font: defaultFont,
4496
+ size: 20,
4497
+ bold: true,
4498
+ color: primaryDarkHex
4499
+ })
4500
+ ],
4501
+ spacing: { after: 240 }
4502
+ })
4503
+ );
4504
+ }
4505
+ elements.push(
4506
+ new import_docx.Paragraph({
4507
+ children: [
4508
+ new import_docx.TextRun({
4509
+ text: backCover.title,
4510
+ font: defaultFont,
4511
+ size: 52,
4512
+ // 26pt
4513
+ bold: true,
4514
+ color: textHex
4515
+ })
4516
+ ],
4517
+ spacing: { after: 140 }
4518
+ })
4519
+ );
4520
+ if (backCover.subtitle) {
4521
+ elements.push(
4522
+ new import_docx.Paragraph({
4523
+ children: [
4524
+ new import_docx.TextRun({
4525
+ text: backCover.subtitle,
4526
+ font: defaultFont,
4527
+ size: 24,
4528
+ // 12pt
4529
+ color: textMutedHex
4530
+ })
4531
+ ],
4532
+ spacing: { after: 480 }
4533
+ })
4534
+ );
4535
+ }
4536
+ elements.push(
4537
+ new import_docx.Paragraph({
4538
+ border: {
4539
+ bottom: { style: import_docx.BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4540
+ },
4541
+ spacing: { after: 480 }
4542
+ })
4543
+ );
4544
+ const contactRuns = [];
4545
+ if (backCover.company) contactRuns.push(new import_docx.TextRun({ text: `Organization: ${backCover.company}
4546
+ `, font: defaultFont, size: 21, color: textHex }));
4547
+ if (backCover.address) contactRuns.push(new import_docx.TextRun({ text: `Address: ${backCover.address}
4548
+ `, font: defaultFont, size: 21, color: textHex }));
4549
+ if (backCover.email) contactRuns.push(new import_docx.TextRun({ text: `Email: ${backCover.email}
4550
+ `, font: defaultFont, size: 21, color: textHex }));
4551
+ if (backCover.phone) contactRuns.push(new import_docx.TextRun({ text: `Phone: ${backCover.phone}
4552
+ `, font: defaultFont, size: 21, color: textHex }));
4553
+ if (backCover.website) contactRuns.push(new import_docx.TextRun({ text: `Website: ${backCover.website}
4554
+ `, font: defaultFont, size: 21, color: textHex }));
4555
+ if (backCover.social) {
4556
+ for (const [net, url] of Object.entries(backCover.social)) {
4557
+ if (url) {
4558
+ contactRuns.push(new import_docx.TextRun({ text: `${net.toUpperCase()}: ${url}
4559
+ `, font: defaultFont, size: 21, color: textHex }));
4560
+ }
4561
+ }
4562
+ }
4563
+ if (contactRuns.length > 0) {
4564
+ elements.push(
4565
+ new import_docx.Paragraph({
4566
+ children: contactRuns,
4567
+ spacing: { before: 360, after: 360 }
4568
+ })
4569
+ );
4570
+ }
4571
+ if (backCover.copyright) {
4572
+ elements.push(
4573
+ new import_docx.Paragraph({
4574
+ children: [
4575
+ new import_docx.TextRun({
4576
+ text: backCover.copyright,
4577
+ font: defaultFont,
4578
+ size: 18,
4579
+ color: "94A3B8"
4580
+ })
4581
+ ],
4582
+ spacing: { before: 720 }
4583
+ })
4584
+ );
4585
+ }
4586
+ return elements;
4587
+ }
4588
+ async function buildDocxCoverPageElements(cover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4589
+ var _a;
4590
+ const elements = [];
4591
+ elements.push(new import_docx.Paragraph({ spacing: { before: 1800 } }));
4592
+ if (cover.logo) {
4593
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
4594
+ if (resolvedLogo) {
4595
+ const logoW = typeof cover.logoWidth === "number" ? cover.logoWidth : 140;
4596
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4597
+ elements.push(
4598
+ new import_docx.Paragraph({
4599
+ children: [
4600
+ new import_docx.ImageRun({
4601
+ data: resolvedLogo.buffer,
4602
+ transformation: {
4603
+ width: logoW,
4604
+ height: Math.round(logoW * 0.75)
4605
+ },
4606
+ type: logoType
4607
+ })
4608
+ ],
4609
+ spacing: { after: 240 }
4610
+ })
4611
+ );
4612
+ }
4613
+ }
4614
+ if (cover.badge) {
4615
+ elements.push(
4616
+ new import_docx.Paragraph({
4617
+ children: [
4618
+ new import_docx.TextRun({
4619
+ text: `[ ${cover.badge.toUpperCase()} ]`,
4620
+ font: defaultFont,
4621
+ size: 20,
4622
+ bold: true,
4623
+ color: primaryDarkHex
4624
+ })
4625
+ ],
4626
+ spacing: { after: 240 }
4627
+ })
4628
+ );
4629
+ }
4630
+ elements.push(
4631
+ new import_docx.Paragraph({
4632
+ children: [
4633
+ new import_docx.TextRun({
4634
+ text: cover.title,
4635
+ font: defaultFont,
4636
+ size: 56,
4637
+ // 28pt
4638
+ bold: true,
4639
+ color: textHex
4640
+ })
4641
+ ],
4642
+ spacing: { after: 140 }
4643
+ })
4644
+ );
4645
+ if (cover.subtitle) {
4646
+ elements.push(
4647
+ new import_docx.Paragraph({
4648
+ children: [
4649
+ new import_docx.TextRun({
4650
+ text: cover.subtitle,
4651
+ font: defaultFont,
4652
+ size: 26,
4653
+ // 13pt
4654
+ color: textMutedHex
4655
+ })
4656
+ ],
4657
+ spacing: { after: 480 }
4658
+ })
4659
+ );
4660
+ }
4661
+ elements.push(
4662
+ new import_docx.Paragraph({
4663
+ border: {
4664
+ bottom: { style: import_docx.BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4665
+ },
4666
+ spacing: { after: 480 }
4667
+ })
4668
+ );
4669
+ if (cover.company || cover.author || cover.version || cover.date) {
4670
+ const metaRuns = [];
4671
+ if (cover.company) metaRuns.push(new import_docx.TextRun({ text: `Organization: ${cover.company}
4672
+ `, font: defaultFont, size: 21, color: textHex }));
4673
+ if (cover.author) metaRuns.push(new import_docx.TextRun({ text: `Author: ${cover.author}
4674
+ `, font: defaultFont, size: 21, color: textHex }));
4675
+ if (cover.version) metaRuns.push(new import_docx.TextRun({ text: `Version: ${cover.version}
4676
+ `, font: defaultFont, size: 21, color: textHex }));
4677
+ if (cover.date) metaRuns.push(new import_docx.TextRun({ text: `Date: ${cover.date}
4678
+ `, font: defaultFont, size: 21, color: textHex }));
4679
+ elements.push(
4680
+ new import_docx.Paragraph({
4681
+ children: metaRuns,
4682
+ spacing: { before: 360, after: 360 }
4683
+ })
4684
+ );
4685
+ }
4686
+ if (cover.footerText) {
4687
+ elements.push(
4688
+ new import_docx.Paragraph({
4689
+ children: [
4690
+ new import_docx.TextRun({
4691
+ text: cover.footerText,
4692
+ font: defaultFont,
4693
+ size: 18,
4694
+ color: "94A3B8"
4695
+ })
4696
+ ],
4697
+ spacing: { before: 720 }
4698
+ })
4699
+ );
4700
+ }
4701
+ return elements;
4702
+ }
3312
4703
  async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
3313
4704
  const cellParagraphs = [];
3314
4705
  if (item.title) {
@@ -3536,14 +4927,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
3536
4927
  };
3537
4928
  }
3538
4929
 
4930
+ // src/server/previewServer.ts
4931
+ var http = __toESM(require("http"));
4932
+ var fs7 = __toESM(require("fs"));
4933
+ var path7 = __toESM(require("path"));
4934
+ async function startPreviewServer(options) {
4935
+ const absoluteFilePath = path7.resolve(process.cwd(), options.filePath);
4936
+ if (!fs7.existsSync(absoluteFilePath)) {
4937
+ throw new Error(`MarkForge preview error: File not found at "${absoluteFilePath}"`);
4938
+ }
4939
+ const baseDir = path7.dirname(absoluteFilePath);
4940
+ const { config: fileConfig } = await loadConfig(void 0, baseDir);
4941
+ const baseConfig = options.config || fileConfig;
4942
+ const port = options.port || 3e3;
4943
+ const sseClients = /* @__PURE__ */ new Set();
4944
+ const broadcastReload = () => {
4945
+ sseClients.forEach((client) => {
4946
+ try {
4947
+ client.write(`event: reload
4948
+ data: ${Date.now()}
4949
+
4950
+ `);
4951
+ } catch {
4952
+ sseClients.delete(client);
4953
+ }
4954
+ });
4955
+ };
4956
+ let debounceTimer = null;
4957
+ const watcher = fs7.watch(baseDir, { recursive: false }, (_event, filename) => {
4958
+ if (!filename) return;
4959
+ const changedPath = path7.resolve(baseDir, filename);
4960
+ if (changedPath === absoluteFilePath || filename.includes("markforge") || filename.endsWith(".css")) {
4961
+ if (debounceTimer) clearTimeout(debounceTimer);
4962
+ debounceTimer = setTimeout(() => {
4963
+ broadcastReload();
4964
+ }, 150);
4965
+ }
4966
+ });
4967
+ const server = http.createServer(async (req, res) => {
4968
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
4969
+ if (url.pathname === "/events") {
4970
+ res.writeHead(200, {
4971
+ "Content-Type": "text/event-stream",
4972
+ "Cache-Control": "no-cache, no-transform",
4973
+ Connection: "keep-alive"
4974
+ });
4975
+ res.write(`data: connected
4976
+
4977
+ `);
4978
+ sseClients.add(res);
4979
+ req.on("close", () => {
4980
+ sseClients.delete(res);
4981
+ });
4982
+ return;
4983
+ }
4984
+ if (url.pathname === "/api/file-content" && req.method === "GET") {
4985
+ try {
4986
+ const content = fs7.readFileSync(absoluteFilePath, "utf-8");
4987
+ res.writeHead(200, { "Content-Type": "application/json" });
4988
+ res.end(
4989
+ JSON.stringify({
4990
+ content,
4991
+ fileName: path7.basename(absoluteFilePath),
4992
+ filePath: absoluteFilePath
4993
+ })
4994
+ );
4995
+ } catch (err) {
4996
+ const msg = err instanceof Error ? err.message : String(err);
4997
+ res.writeHead(500, { "Content-Type": "application/json" });
4998
+ res.end(JSON.stringify({ error: msg }));
4999
+ }
5000
+ return;
5001
+ }
5002
+ if (url.pathname === "/api/save-content" && req.method === "POST") {
5003
+ let body = "";
5004
+ req.on("data", (chunk) => {
5005
+ body += chunk;
5006
+ });
5007
+ req.on("end", () => {
5008
+ try {
5009
+ const parsed = JSON.parse(body);
5010
+ if (typeof parsed.content === "string") {
5011
+ fs7.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
5012
+ broadcastReload();
5013
+ res.writeHead(200, { "Content-Type": "application/json" });
5014
+ res.end(JSON.stringify({ success: true, savedAt: Date.now() }));
5015
+ } else {
5016
+ res.writeHead(400, { "Content-Type": "application/json" });
5017
+ res.end(JSON.stringify({ error: "Missing content field in request body" }));
5018
+ }
5019
+ } catch (err) {
5020
+ const msg = err instanceof Error ? err.message : String(err);
5021
+ res.writeHead(500, { "Content-Type": "application/json" });
5022
+ res.end(JSON.stringify({ error: msg }));
5023
+ }
5024
+ });
5025
+ return;
5026
+ }
5027
+ if (url.pathname === "/api/export" && (req.method === "GET" || req.method === "POST")) {
5028
+ const format = url.searchParams.get("format") || "docx";
5029
+ try {
5030
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5031
+ const doc = parseMarkdownDocument(mdContent);
5032
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5033
+ const mergedConfig = { ...baseConfig, ...resolvedConfig };
5034
+ const fileBase = path7.basename(absoluteFilePath, path7.extname(absoluteFilePath));
5035
+ if (format === "docx") {
5036
+ const buffer = await buildDocxDocument(doc, mergedConfig, baseDir);
5037
+ res.writeHead(200, {
5038
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5039
+ "Content-Disposition": `attachment; filename="${fileBase}.docx"`
5040
+ });
5041
+ res.end(buffer);
5042
+ return;
5043
+ } else if (format === "pdf") {
5044
+ const buffer = await buildPdfDocument(doc, mergedConfig, baseDir);
5045
+ res.writeHead(200, {
5046
+ "Content-Type": "application/pdf",
5047
+ "Content-Disposition": `attachment; filename="${fileBase}.pdf"`
5048
+ });
5049
+ res.end(buffer);
5050
+ return;
5051
+ } else {
5052
+ const html = await buildHtmlDocument(doc, mergedConfig, baseDir);
5053
+ res.writeHead(200, {
5054
+ "Content-Type": "text/html; charset=utf-8",
5055
+ "Content-Disposition": `attachment; filename="${fileBase}.html"`
5056
+ });
5057
+ res.end(html);
5058
+ return;
5059
+ }
5060
+ } catch (err) {
5061
+ const msg = err instanceof Error ? err.message : String(err);
5062
+ res.writeHead(500, { "Content-Type": "text/plain" });
5063
+ res.end(`Export failed: ${msg}`);
5064
+ return;
5065
+ }
5066
+ }
5067
+ if (url.pathname === "/document-content") {
5068
+ try {
5069
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5070
+ const doc = parseMarkdownDocument(mdContent);
5071
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5072
+ const html = await buildHtmlDocument(doc, { ...baseConfig, ...resolvedConfig }, baseDir);
5073
+ const injectedScript = `
5074
+ <script>
5075
+ (function() {
5076
+ var evtSource = new EventSource('/events');
5077
+ evtSource.addEventListener('reload', function() {
5078
+ var scrollPos = window.scrollY;
5079
+ sessionStorage.setItem('markforge_scroll', scrollPos);
5080
+ window.location.reload();
5081
+ });
5082
+ window.addEventListener('load', function() {
5083
+ var saved = sessionStorage.getItem('markforge_scroll');
5084
+ if (saved) {
5085
+ window.scrollTo(0, parseInt(saved, 10));
5086
+ }
5087
+ });
5088
+ })();
5089
+ </script>
5090
+ `;
5091
+ const finalHtml = html.replace("</body>", `${injectedScript}</body>`);
5092
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5093
+ res.end(finalHtml);
5094
+ } catch (err) {
5095
+ const msg = err instanceof Error ? err.message : String(err);
5096
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
5097
+ 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>`);
5098
+ }
5099
+ return;
5100
+ }
5101
+ if (url.pathname === "/" || url.pathname === "/index.html") {
5102
+ const fileName = path7.basename(absoluteFilePath);
5103
+ const initialContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5104
+ const appHtml = `<!DOCTYPE html>
5105
+ <html lang="en">
5106
+ <head>
5107
+ <meta charset="UTF-8">
5108
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5109
+ <title>MarkForge Live Studio - ${escapeHtml2(fileName)}</title>
5110
+ <link rel="preconnect" href="https://fonts.googleapis.com">
5111
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5112
+ <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">
5113
+ <style>
5114
+ :root {
5115
+ --mf-primary: #0D998D;
5116
+ --mf-primary-dark: #008277;
5117
+ --mf-primary-light: #ECFDFD;
5118
+ --mf-primary-border: #33CDCF;
5119
+ --mf-dark: #0F172A;
5120
+ --mf-slate: #1E293B;
5121
+ --mf-editor-bg: #0F172A;
5122
+ --mf-editor-gutter: #1E293B;
5123
+ --mf-editor-text: #F8FAFC;
5124
+ --mf-muted: #64748B;
5125
+ --mf-light-border: #E2E8F0;
5126
+ --mf-bg: #F1F5F9;
5127
+ }
5128
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5129
+ body {
5130
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5131
+ background: var(--mf-bg);
5132
+ color: var(--mf-dark);
5133
+ display: flex;
5134
+ flex-direction: column;
5135
+ height: 100vh;
5136
+ overflow: hidden;
5137
+ }
5138
+ header {
5139
+ background: #FFFFFF;
5140
+ border-bottom: 1px solid var(--mf-light-border);
5141
+ min-height: 56px;
5142
+ display: flex;
5143
+ flex-wrap: wrap;
5144
+ align-items: center;
5145
+ justify-content: space-between;
5146
+ padding: 0.4rem 1.2rem;
5147
+ z-index: 10;
5148
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
5149
+ gap: 0.75rem;
5150
+ }
5151
+ .brand-section {
5152
+ display: flex;
5153
+ align-items: center;
5154
+ gap: 0.75rem;
5155
+ }
5156
+ .brand-badge {
5157
+ font-size: 0.72rem;
5158
+ font-weight: 800;
5159
+ letter-spacing: 0.08em;
5160
+ background: var(--mf-dark);
5161
+ color: #FFFFFF;
5162
+ padding: 0.25rem 0.55rem;
5163
+ border-radius: 4px;
5164
+ text-transform: uppercase;
5165
+ }
5166
+ .file-name {
5167
+ font-size: 0.9rem;
5168
+ font-weight: 700;
5169
+ color: var(--mf-dark);
5170
+ }
5171
+ .sync-status {
5172
+ display: flex;
5173
+ align-items: center;
5174
+ gap: 0.35rem;
5175
+ font-size: 0.75rem;
5176
+ font-weight: 600;
5177
+ color: var(--mf-primary-dark);
5178
+ background: var(--mf-primary-light);
5179
+ padding: 0.2rem 0.55rem;
5180
+ border-radius: 9999px;
5181
+ border: 1px solid var(--mf-primary-border);
5182
+ }
5183
+ .sync-dot {
5184
+ width: 7px;
5185
+ height: 7px;
5186
+ background-color: var(--mf-primary);
5187
+ border-radius: 50%;
5188
+ box-shadow: 0 0 0 2px rgba(13, 153, 141, 0.2);
5189
+ }
5190
+ .toolbar-section {
5191
+ display: flex;
5192
+ align-items: center;
5193
+ gap: 0.3rem;
5194
+ background: #F8FAFC;
5195
+ padding: 0.25rem 0.4rem;
5196
+ border-radius: 6px;
5197
+ border: 1px solid var(--mf-light-border);
5198
+ }
5199
+ .tool-btn {
5200
+ font-family: 'JetBrains Mono', monospace;
5201
+ font-size: 0.75rem;
5202
+ font-weight: 600;
5203
+ padding: 0.25rem 0.45rem;
5204
+ background: transparent;
5205
+ border: 1px solid transparent;
5206
+ border-radius: 4px;
5207
+ cursor: pointer;
5208
+ color: var(--mf-slate);
5209
+ transition: all 0.1s ease;
5210
+ }
5211
+ .tool-btn:hover {
5212
+ background: #FFFFFF;
5213
+ border-color: var(--mf-light-border);
5214
+ color: var(--mf-primary-dark);
5215
+ }
5216
+ .tool-divider {
5217
+ width: 1px;
5218
+ height: 16px;
5219
+ background: var(--mf-light-border);
5220
+ margin: 0 0.15rem;
5221
+ }
5222
+ .controls {
5223
+ display: flex;
5224
+ align-items: center;
5225
+ gap: 0.5rem;
5226
+ }
5227
+ .view-toggles {
5228
+ display: flex;
5229
+ background: #F1F5F9;
5230
+ padding: 2px;
5231
+ border-radius: 6px;
5232
+ border: 1px solid var(--mf-light-border);
5233
+ }
5234
+ .toggle-btn {
5235
+ font-family: inherit;
5236
+ font-size: 0.74rem;
5237
+ font-weight: 600;
5238
+ padding: 0.25rem 0.55rem;
5239
+ border: none;
5240
+ background: transparent;
5241
+ border-radius: 4px;
5242
+ cursor: pointer;
5243
+ color: var(--mf-muted);
5244
+ transition: all 0.15s ease;
5245
+ }
5246
+ .toggle-btn.active {
5247
+ background: #FFFFFF;
5248
+ color: var(--mf-dark);
5249
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
5250
+ }
5251
+ .btn {
5252
+ font-family: inherit;
5253
+ font-size: 0.78rem;
5254
+ font-weight: 600;
5255
+ padding: 0.35rem 0.75rem;
5256
+ border-radius: 6px;
5257
+ cursor: pointer;
5258
+ text-decoration: none;
5259
+ transition: all 0.15s ease;
5260
+ display: inline-flex;
5261
+ align-items: center;
5262
+ gap: 0.3rem;
5263
+ border: 1px solid var(--mf-light-border);
5264
+ background: #FFFFFF;
5265
+ color: var(--mf-dark);
5266
+ }
5267
+ .btn:hover {
5268
+ background: #F8FAFC;
5269
+ border-color: #CBD5E1;
5270
+ }
5271
+ .btn-primary {
5272
+ background: var(--mf-primary);
5273
+ color: #FFFFFF;
5274
+ border-color: var(--mf-primary);
5275
+ }
5276
+ .btn-primary:hover {
5277
+ background: var(--mf-primary-dark);
5278
+ border-color: var(--mf-primary-dark);
5279
+ }
5280
+ .save-indicator {
5281
+ font-size: 0.75rem;
5282
+ font-weight: 600;
5283
+ color: var(--mf-muted);
5284
+ min-width: 65px;
5285
+ text-align: right;
5286
+ }
5287
+ .save-indicator.saved {
5288
+ color: var(--mf-primary-dark);
5289
+ }
5290
+ .save-indicator.saving {
5291
+ color: #D97706;
5292
+ }
5293
+ .save-indicator.unsaved {
5294
+ color: #E11D48;
5295
+ }
5296
+
5297
+ /* Main Workspace Splitter Layout */
5298
+ main.workspace {
5299
+ flex: 1;
5300
+ display: flex;
5301
+ height: calc(100vh - 56px);
5302
+ overflow: hidden;
5303
+ background: var(--mf-bg);
5304
+ position: relative;
5305
+ }
5306
+ .editor-pane {
5307
+ width: 50%;
5308
+ height: 100%;
5309
+ display: flex;
5310
+ flex-direction: column;
5311
+ background: var(--mf-editor-bg);
5312
+ border-right: 1px solid #334155;
5313
+ overflow: hidden;
5314
+ }
5315
+ .editor-header {
5316
+ background: #090D16;
5317
+ border-bottom: 1px solid #1E293B;
5318
+ padding: 0.4rem 0.8rem;
5319
+ display: flex;
5320
+ align-items: center;
5321
+ justify-content: space-between;
5322
+ color: #94A3B8;
5323
+ font-size: 0.74rem;
5324
+ font-weight: 500;
5325
+ }
5326
+ .editor-container {
5327
+ flex: 1;
5328
+ display: flex;
5329
+ position: relative;
5330
+ overflow: hidden;
5331
+ background: var(--mf-editor-bg);
5332
+ }
5333
+ .line-numbers {
5334
+ width: 44px;
5335
+ padding: 0.8rem 0.4rem;
5336
+ font-family: 'JetBrains Mono', monospace;
5337
+ font-size: 13px;
5338
+ line-height: 1.55;
5339
+ color: #475569;
5340
+ text-align: right;
5341
+ user-select: none;
5342
+ background: var(--mf-editor-gutter);
5343
+ overflow: hidden;
5344
+ border-right: 1px solid #1E293B;
5345
+ }
5346
+ .code-editor {
5347
+ flex: 1;
5348
+ padding: 0.8rem 1rem;
5349
+ font-family: 'JetBrains Mono', monospace;
5350
+ font-size: 13px;
5351
+ line-height: 1.55;
5352
+ color: var(--mf-editor-text);
5353
+ background: transparent;
5354
+ border: none;
5355
+ outline: none;
5356
+ resize: none;
5357
+ white-space: pre;
5358
+ overflow-wrap: normal;
5359
+ overflow: auto;
5360
+ tab-size: 2;
5361
+ }
5362
+
5363
+ /* Draggable Splitter Handle */
5364
+ .splitter {
5365
+ width: 8px;
5366
+ cursor: col-resize;
5367
+ background: #E2E8F0;
5368
+ transition: background 0.15s ease;
5369
+ position: relative;
5370
+ z-index: 5;
5371
+ }
5372
+ .splitter:hover, .splitter.active {
5373
+ background: var(--mf-primary);
5374
+ }
5375
+
5376
+ /* Right Preview Pane */
5377
+ .preview-pane {
5378
+ width: 50%;
5379
+ height: 100%;
5380
+ display: flex;
5381
+ flex-direction: column;
5382
+ background: #FFFFFF;
5383
+ overflow: hidden;
5384
+ }
5385
+ .preview-header {
5386
+ background: #FFFFFF;
5387
+ border-bottom: 1px solid var(--mf-light-border);
5388
+ padding: 0.35rem 0.8rem;
5389
+ display: flex;
5390
+ align-items: center;
5391
+ justify-content: space-between;
5392
+ color: var(--mf-muted);
5393
+ font-size: 0.74rem;
5394
+ font-weight: 600;
5395
+ }
5396
+ .viewport-selector {
5397
+ display: flex;
5398
+ gap: 0.25rem;
5399
+ }
5400
+ .vp-btn {
5401
+ font-size: 0.72rem;
5402
+ padding: 0.15rem 0.4rem;
5403
+ border: 1px solid var(--mf-light-border);
5404
+ background: #F8FAFC;
5405
+ border-radius: 4px;
5406
+ cursor: pointer;
5407
+ color: var(--mf-muted);
5408
+ }
5409
+ .vp-btn.active {
5410
+ background: var(--mf-primary-light);
5411
+ color: var(--mf-primary-dark);
5412
+ border-color: var(--mf-primary-border);
5413
+ }
5414
+ .preview-wrapper {
5415
+ flex: 1;
5416
+ display: flex;
5417
+ justify-content: center;
5418
+ align-items: stretch;
5419
+ background: #F1F5F9;
5420
+ overflow: hidden;
5421
+ }
5422
+ iframe {
5423
+ width: 100%;
5424
+ height: 100%;
5425
+ border: none;
5426
+ background: #FFFFFF;
5427
+ transition: max-width 0.2s ease;
5428
+ }
5429
+ .author-footer {
5430
+ font-size: 0.72rem;
5431
+ color: var(--mf-muted);
5432
+ padding-right: 0.5rem;
5433
+ }
5434
+ .author-footer a {
5435
+ color: var(--mf-primary-dark);
5436
+ text-decoration: none;
5437
+ font-weight: 600;
5438
+ }
5439
+ </style>
5440
+ </head>
5441
+ <body>
5442
+ <header>
5443
+ <div class="brand-section">
5444
+ <span class="brand-badge">MARKFORGE STUDIO</span>
5445
+ <span class="file-name" title="${escapeHtml2(absoluteFilePath)}">${escapeHtml2(fileName)}</span>
5446
+ <div class="sync-status">
5447
+ <div class="sync-dot"></div>
5448
+ <span>Live Sync Active</span>
5449
+ </div>
5450
+ </div>
5451
+
5452
+ <!-- Quick Formatting Toolbar -->
5453
+ <div class="toolbar-section">
5454
+ <button class="tool-btn" onclick="insertFormat('h1')" title="Heading 1">H1</button>
5455
+ <button class="tool-btn" onclick="insertFormat('h2')" title="Heading 2">H2</button>
5456
+ <button class="tool-btn" onclick="insertFormat('h3')" title="Heading 3">H3</button>
5457
+ <div class="tool-divider"></div>
5458
+ <button class="tool-btn" onclick="insertFormat('bold')" title="Bold">B</button>
5459
+ <button class="tool-btn" onclick="insertFormat('italic')" title="Italic">I</button>
5460
+ <button class="tool-btn" onclick="insertFormat('code')" title="Inline Code">&lt;&gt;</button>
5461
+ <button class="tool-btn" onclick="insertFormat('quote')" title="Blockquote">&gt;</button>
5462
+ <div class="tool-divider"></div>
5463
+ <button class="tool-btn" onclick="insertFormat('table')" title="GFM Table">Table</button>
5464
+ <button class="tool-btn" onclick="insertFormat('list')" title="List">List</button>
5465
+ <button class="tool-btn" onclick="insertFormat('task')" title="Task Checklist">Task</button>
5466
+ <div class="tool-divider"></div>
5467
+ <button class="tool-btn" onclick="insertFormat('callout')" title="Callout Box">Callout</button>
5468
+ <button class="tool-btn" onclick="insertFormat('math')" title="LaTeX Math">Math</button>
5469
+ <button class="tool-btn" onclick="insertFormat('columns')" title="Multi-Columns">Columns</button>
5470
+ <button class="tool-btn" onclick="insertFormat('footnote')" title="Footnote">Footnote</button>
5471
+ <button class="tool-btn" onclick="insertFormat('mermaid')" title="Mermaid Diagram">Mermaid</button>
5472
+ </div>
5473
+
5474
+ <!-- Controls & View Mode -->
5475
+ <div class="controls">
5476
+ <div class="view-toggles">
5477
+ <button class="toggle-btn active" id="btn-split" onclick="setViewMode('split')">Split</button>
5478
+ <button class="toggle-btn" id="btn-edit" onclick="setViewMode('edit')">Editor</button>
5479
+ <button class="toggle-btn" id="btn-prev" onclick="setViewMode('prev')">Preview</button>
5480
+ </div>
5481
+ <span class="save-indicator saved" id="save-status">Saved</span>
5482
+ <button class="btn btn-primary" onclick="saveContentManual()" title="Save (Ctrl+S)">Save</button>
5483
+ <button class="btn" onclick="exportDoc('docx')" title="Download Word Document">DOCX</button>
5484
+ <button class="btn" onclick="exportDoc('pdf')" title="Download PDF Document">PDF</button>
5485
+ <button class="btn" onclick="printDoc()" title="Print / PDF dialog">Print</button>
5486
+ </div>
5487
+ </header>
5488
+
5489
+ <main class="workspace" id="workspace">
5490
+ <!-- Left: Code Editor Pane -->
5491
+ <div class="editor-pane" id="editor-pane">
5492
+ <div class="editor-header">
5493
+ <span>MARKDOWN SOURCE</span>
5494
+ <span id="editor-stats">Lines: 1 | Words: 0 | UTF-8</span>
5495
+ </div>
5496
+ <div class="editor-container">
5497
+ <div class="line-numbers" id="line-numbers">1</div>
5498
+ <textarea class="code-editor" id="code-editor" spellcheck="false" placeholder="Write markdown here...">${escapeHtml2(initialContent)}</textarea>
5499
+ </div>
5500
+ </div>
5501
+
5502
+ <!-- Middle: Draggable Splitter Handle -->
5503
+ <div class="splitter" id="splitter"></div>
5504
+
5505
+ <!-- Right: Rendered Preview Pane -->
5506
+ <div class="preview-pane" id="preview-pane">
5507
+ <div class="preview-header">
5508
+ <span>RENDERED PREVIEW</span>
5509
+ <div class="viewport-selector">
5510
+ <button class="vp-btn active" onclick="setViewport('100%')" id="vp-full">100% Full</button>
5511
+ <button class="vp-btn" onclick="setViewport('820px')" id="vp-a4">A4 (820px)</button>
5512
+ <button class="vp-btn" onclick="setViewport('440px')" id="vp-mob">Mobile</button>
5513
+ </div>
5514
+ <span class="author-footer">Created by <a href="https://github.com/masumrpg" target="_blank">Ma'sum (@masumrpg)</a></span>
5515
+ </div>
5516
+ <div class="preview-wrapper">
5517
+ <iframe id="preview-frame" src="/document-content"></iframe>
5518
+ </div>
5519
+ </div>
5520
+ </main>
5521
+
5522
+ <script>
5523
+ var editor = document.getElementById('code-editor');
5524
+ var lineNumbers = document.getElementById('line-numbers');
5525
+ var stats = document.getElementById('editor-stats');
5526
+ var saveStatus = document.getElementById('save-status');
5527
+ var previewFrame = document.getElementById('preview-frame');
5528
+ var editorPane = document.getElementById('editor-pane');
5529
+ var previewPane = document.getElementById('preview-pane');
5530
+ var splitter = document.getElementById('splitter');
5531
+ var isDirty = false;
5532
+ var autoSaveTimeout = null;
5533
+
5534
+ // Update Line Numbers & Stats
5535
+ function updateStatsAndLines() {
5536
+ var lines = editor.value.split('\\n');
5537
+ var lineCount = lines.length;
5538
+ var numHtml = '';
5539
+ for (var i = 1; i <= lineCount; i++) {
5540
+ numHtml += i + '<br>';
5541
+ }
5542
+ lineNumbers.innerHTML = numHtml;
5543
+
5544
+ var words = editor.value.trim().length > 0 ? editor.value.trim().split(/\\s+/).length : 0;
5545
+ var chars = editor.value.length;
5546
+ stats.textContent = 'Lines: ' + lineCount + ' | Words: ' + words + ' | Chars: ' + chars + ' | UTF-8';
5547
+ }
5548
+
5549
+ // Synchronize vertical scroll between Line Numbers and Textarea
5550
+ editor.addEventListener('scroll', function() {
5551
+ lineNumbers.scrollTop = editor.scrollTop;
5552
+ });
5553
+
5554
+ // Handle Input & Debounced Auto-Save
5555
+ editor.addEventListener('input', function() {
5556
+ updateStatsAndLines();
5557
+ setSaveState('unsaved');
5558
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5559
+ autoSaveTimeout = setTimeout(function() {
5560
+ saveContent();
5561
+ }, 600);
5562
+ });
5563
+
5564
+ function setSaveState(state) {
5565
+ if (state === 'saved') {
5566
+ saveStatus.textContent = 'Saved';
5567
+ saveStatus.className = 'save-indicator saved';
5568
+ isDirty = false;
5569
+ } else if (state === 'saving') {
5570
+ saveStatus.textContent = 'Saving...';
5571
+ saveStatus.className = 'save-indicator saving';
5572
+ } else {
5573
+ saveStatus.textContent = 'Changes...';
5574
+ saveStatus.className = 'save-indicator unsaved';
5575
+ isDirty = true;
5576
+ }
5577
+ }
5578
+
5579
+ // Save Content via API
5580
+ function saveContent(callback) {
5581
+ setSaveState('saving');
5582
+ fetch('/api/save-content', {
5583
+ method: 'POST',
5584
+ headers: { 'Content-Type': 'application/json' },
5585
+ body: JSON.stringify({ content: editor.value }),
5586
+ })
5587
+ .then(function(res) { return res.json(); })
5588
+ .then(function(data) {
5589
+ if (data.success) {
5590
+ setSaveState('saved');
5591
+ if (callback) callback();
5592
+ } else {
5593
+ saveStatus.textContent = 'Save Error';
5594
+ }
5595
+ })
5596
+ .catch(function() {
5597
+ saveStatus.textContent = 'Save Error';
5598
+ });
5599
+ }
5600
+
5601
+ function saveContentManual() {
5602
+ saveContent();
5603
+ }
5604
+
5605
+ // Keyboard Shortcuts: Tab (2 spaces), Shift+Tab, Ctrl+S
5606
+ editor.addEventListener('keydown', function(e) {
5607
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
5608
+ e.preventDefault();
5609
+ saveContent();
5610
+ return;
5611
+ }
5612
+
5613
+ if (e.key === 'Tab') {
5614
+ e.preventDefault();
5615
+ var start = this.selectionStart;
5616
+ var end = this.selectionEnd;
5617
+ this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
5618
+ this.selectionStart = this.selectionEnd = start + 2;
5619
+ updateStatsAndLines();
5620
+ setSaveState('unsaved');
5621
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5622
+ autoSaveTimeout = setTimeout(saveContent, 600);
5623
+ }
5624
+ });
5625
+
5626
+ // Formatting Snippet Injector
5627
+ function insertFormat(type) {
5628
+ var start = editor.selectionStart;
5629
+ var end = editor.selectionEnd;
5630
+ var selected = editor.value.substring(start, end);
5631
+ var replacement = '';
5632
+
5633
+ switch (type) {
5634
+ case 'h1': replacement = '# ' + (selected || 'Heading 1'); break;
5635
+ case 'h2': replacement = '## ' + (selected || 'Heading 2'); break;
5636
+ case 'h3': replacement = '### ' + (selected || 'Heading 3'); break;
5637
+ case 'bold': replacement = '**' + (selected || 'bold text') + '**'; break;
5638
+ case 'italic': replacement = '*' + (selected || 'italic text') + '*'; break;
5639
+ case 'code': replacement = '\`' + (selected || 'inline code') + '\`'; break;
5640
+ case 'quote': replacement = '> ' + (selected || 'Quote text'); break;
5641
+ case 'table':
5642
+ replacement = '\\n| Column 1 | Column 2 | Column 3 |\\n| :--- | :---: | ---: |\\n| Data A | Data B | Data C |\\n| Data D | Data E | Data F |\\n';
5643
+ break;
5644
+ case 'list': replacement = '- ' + (selected || 'List item'); break;
5645
+ case 'task': replacement = '- [ ] ' + (selected || 'Task item'); break;
5646
+ case 'callout':
5647
+ replacement = '> [!NOTE]\\n> ' + (selected || 'This is an important callout note.');
5648
+ break;
5649
+ case 'math':
5650
+ replacement = '$$\\n' + (selected || '\\\\int_{-\\\\infty}^{\\\\infty} e^{-x^2} dx = \\\\sqrt{\\\\pi}') + '\\n$$';
5651
+ break;
5652
+ case 'columns':
5653
+ 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:::';
5654
+ break;
5655
+ case 'footnote':
5656
+ replacement = (selected || 'Statement with footnote') + '[^1]\\n\\n[^1]: Note description text.';
5657
+ break;
5658
+ case 'mermaid':
5659
+ 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';
5660
+ break;
5661
+ }
5662
+
5663
+ editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
5664
+ editor.selectionStart = editor.selectionEnd = start + replacement.length;
5665
+ editor.focus();
5666
+ updateStatsAndLines();
5667
+ setSaveState('unsaved');
5668
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5669
+ autoSaveTimeout = setTimeout(saveContent, 600);
5670
+ }
5671
+
5672
+ // View Mode Toggle (Split / Editor Only / Preview Only)
5673
+ function setViewMode(mode) {
5674
+ document.getElementById('btn-split').classList.remove('active');
5675
+ document.getElementById('btn-edit').classList.remove('active');
5676
+ document.getElementById('btn-prev').classList.remove('active');
5677
+
5678
+ if (mode === 'split') {
5679
+ document.getElementById('btn-split').classList.add('active');
5680
+ editorPane.style.display = 'flex';
5681
+ editorPane.style.width = '50%';
5682
+ previewPane.style.display = 'flex';
5683
+ previewPane.style.width = '50%';
5684
+ splitter.style.display = 'block';
5685
+ } else if (mode === 'edit') {
5686
+ document.getElementById('btn-edit').classList.add('active');
5687
+ editorPane.style.display = 'flex';
5688
+ editorPane.style.width = '100%';
5689
+ previewPane.style.display = 'none';
5690
+ splitter.style.display = 'none';
5691
+ } else if (mode === 'prev') {
5692
+ document.getElementById('btn-prev').classList.add('active');
5693
+ editorPane.style.display = 'none';
5694
+ previewPane.style.display = 'flex';
5695
+ previewPane.style.width = '100%';
5696
+ splitter.style.display = 'none';
5697
+ }
5698
+ }
5699
+
5700
+ // Viewport Width Resizer
5701
+ function setViewport(width) {
5702
+ document.getElementById('vp-full').classList.remove('active');
5703
+ document.getElementById('vp-a4').classList.remove('active');
5704
+ document.getElementById('vp-mob').classList.remove('active');
5705
+
5706
+ if (width === '100%') {
5707
+ document.getElementById('vp-full').classList.add('active');
5708
+ previewFrame.style.maxWidth = '100%';
5709
+ } else if (width === '820px') {
5710
+ document.getElementById('vp-a4').classList.add('active');
5711
+ previewFrame.style.maxWidth = '820px';
5712
+ } else if (width === '440px') {
5713
+ document.getElementById('vp-mob').classList.add('active');
5714
+ previewFrame.style.maxWidth = '440px';
5715
+ }
5716
+ }
5717
+
5718
+ // Draggable Splitter Handle Logic
5719
+ var isDragging = false;
5720
+ splitter.addEventListener('mousedown', function(e) {
5721
+ isDragging = true;
5722
+ splitter.classList.add('active');
5723
+ document.body.style.cursor = 'col-resize';
5724
+ document.body.style.userSelect = 'none';
5725
+ });
5726
+
5727
+ window.addEventListener('mousemove', function(e) {
5728
+ if (!isDragging) return;
5729
+ var totalWidth = document.getElementById('workspace').clientWidth;
5730
+ var newEditorWidth = (e.clientX / totalWidth) * 100;
5731
+ if (newEditorWidth > 15 && newEditorWidth < 85) {
5732
+ editorPane.style.width = newEditorWidth + '%';
5733
+ previewPane.style.width = (100 - newEditorWidth) + '%';
5734
+ }
5735
+ });
5736
+
5737
+ window.addEventListener('mouseup', function() {
5738
+ if (isDragging) {
5739
+ isDragging = false;
5740
+ splitter.classList.remove('active');
5741
+ document.body.style.cursor = '';
5742
+ document.body.style.userSelect = '';
5743
+ }
5744
+ });
5745
+
5746
+ // Document Print Action
5747
+ function printDoc() {
5748
+ previewFrame.contentWindow.print();
5749
+ }
5750
+
5751
+ // Document Export Action
5752
+ function exportDoc(format) {
5753
+ saveContent(function() {
5754
+ window.location.href = '/api/export?format=' + format;
5755
+ });
5756
+ }
5757
+
5758
+ // Initialize line stats
5759
+ updateStatsAndLines();
5760
+ </script>
5761
+ </body>
5762
+ </html>`;
5763
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5764
+ res.end(appHtml);
5765
+ return;
5766
+ }
5767
+ res.writeHead(404, { "Content-Type": "text/plain" });
5768
+ res.end("Not Found");
5769
+ });
5770
+ return new Promise((resolve6, reject) => {
5771
+ server.listen(port, () => {
5772
+ const url = `http://localhost:${port}`;
5773
+ resolve6({
5774
+ server,
5775
+ port,
5776
+ url,
5777
+ close: async () => {
5778
+ watcher.close();
5779
+ sseClients.forEach((client) => {
5780
+ try {
5781
+ client.end();
5782
+ } catch {
5783
+ }
5784
+ });
5785
+ sseClients.clear();
5786
+ return new Promise((res) => {
5787
+ server.close(() => res());
5788
+ });
5789
+ }
5790
+ });
5791
+ });
5792
+ server.on("error", (err) => {
5793
+ reject(err);
5794
+ });
5795
+ });
5796
+ }
5797
+ function escapeHtml2(str) {
5798
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5799
+ }
5800
+
3539
5801
  // src/config/defineConfig.ts
3540
5802
  function defineConfig(config) {
3541
5803
  return config;
3542
5804
  }
3543
5805
 
3544
5806
  // src/version.ts
3545
- var fs7 = __toESM(require("fs"));
3546
- var path7 = __toESM(require("path"));
5807
+ var fs8 = __toESM(require("fs"));
5808
+ var path8 = __toESM(require("path"));
3547
5809
  var import_node_url4 = require("url");
3548
5810
  var import_meta = {};
3549
5811
  try {
@@ -3566,21 +5828,21 @@ try {
3566
5828
  }
3567
5829
  } catch {
3568
5830
  }
3569
- var FALLBACK_VERSION = "0.3.0";
5831
+ var FALLBACK_VERSION = "0.4.0";
3570
5832
  function readVersionFromPackageJson(fromDir) {
3571
5833
  let currentDir = fromDir;
3572
5834
  for (let i = 0; i < 6; i++) {
3573
5835
  try {
3574
- const pkgJsonPath = path7.join(currentDir, "package.json");
3575
- if (fs7.existsSync(pkgJsonPath)) {
3576
- const pkg = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5836
+ const pkgJsonPath = path8.join(currentDir, "package.json");
5837
+ if (fs8.existsSync(pkgJsonPath)) {
5838
+ const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
3577
5839
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
3578
5840
  return pkg.version;
3579
5841
  }
3580
5842
  }
3581
5843
  } catch {
3582
5844
  }
3583
- const parentDir = path7.dirname(currentDir);
5845
+ const parentDir = path8.dirname(currentDir);
3584
5846
  if (parentDir === currentDir) break;
3585
5847
  currentDir = parentDir;
3586
5848
  }
@@ -3591,7 +5853,7 @@ function getPackageDir() {
3591
5853
  return __dirname;
3592
5854
  }
3593
5855
  try {
3594
- return path7.dirname((0, import_node_url4.fileURLToPath)(import_meta.url));
5856
+ return path8.dirname((0, import_node_url4.fileURLToPath)(import_meta.url));
3595
5857
  } catch {
3596
5858
  return process.cwd();
3597
5859
  }
@@ -3603,6 +5865,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3603
5865
  // Annotate the CommonJS export names for ESM import in node:
3604
5866
  0 && (module.exports = {
3605
5867
  DEFAULT_CONFIG,
5868
+ KATEX_INLINE_CSS,
3606
5869
  MARKFORGE_VERSION,
3607
5870
  Orientation,
3608
5871
  OutputFormat,
@@ -3631,18 +5894,28 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3631
5894
  inlineHtmlImages,
3632
5895
  loadConfig,
3633
5896
  markforge,
5897
+ normalizeBackCover,
5898
+ normalizeCoverPage,
3634
5899
  normalizeHeaderFooter,
3635
5900
  normalizeHeaderFooterSlot,
5901
+ normalizeNumberHeadings,
5902
+ normalizeSecurity,
3636
5903
  normalizeSignatures,
3637
5904
  normalizeWatermark,
3638
5905
  parseInlineSpans,
3639
5906
  parseMarginToTwip,
5907
+ parseMarkdown,
3640
5908
  parseMarkdownDocument,
5909
+ renderBackCoverHtml,
5910
+ renderCoverPageHtml,
3641
5911
  renderInlinesToHtml,
5912
+ renderMathToHtml,
3642
5913
  renderMermaidToPng,
5914
+ renderNodesToHtml,
3643
5915
  replaceDocumentTokens,
3644
5916
  resolveDocumentConfig,
3645
5917
  resolveImage,
3646
5918
  slugify,
5919
+ startPreviewServer,
3647
5920
  tokenizeCodeLine
3648
5921
  });