@masumdev/markforge 0.2.5 → 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,18 +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,
77
+ normalizeSignatures: () => normalizeSignatures,
72
78
  normalizeWatermark: () => normalizeWatermark,
73
79
  parseInlineSpans: () => parseInlineSpans,
74
80
  parseMarginToTwip: () => parseMarginToTwip2,
81
+ parseMarkdown: () => parseMarkdownDocument,
75
82
  parseMarkdownDocument: () => parseMarkdownDocument,
83
+ renderBackCoverHtml: () => renderBackCoverHtml,
84
+ renderCoverPageHtml: () => renderCoverPageHtml,
76
85
  renderInlinesToHtml: () => renderInlinesToHtml,
86
+ renderMathToHtml: () => renderMathToHtml,
77
87
  renderMermaidToPng: () => renderMermaidToPng,
88
+ renderNodesToHtml: () => renderNodesToHtml,
78
89
  replaceDocumentTokens: () => replaceDocumentTokens,
79
90
  resolveDocumentConfig: () => resolveDocumentConfig,
80
91
  resolveImage: () => resolveImage,
81
92
  slugify: () => slugify,
93
+ startPreviewServer: () => startPreviewServer,
82
94
  tokenizeCodeLine: () => tokenizeCodeLine
83
95
  });
84
96
  module.exports = __toCommonJS(src_exports);
@@ -119,6 +131,26 @@ function parseInlineSpans(text) {
119
131
  remaining = remaining.slice(imgMatch[0].length);
120
132
  continue;
121
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
+ }
122
154
  const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
123
155
  if (linkMatch) {
124
156
  spans.push({
@@ -218,7 +250,7 @@ function parseInlineSpans(text) {
218
250
  remaining = remaining.slice(fullTag.length);
219
251
  continue;
220
252
  }
221
- const nextSpecial = remaining.search(/[\*\_\[\!`~<]/);
253
+ const nextSpecial = remaining.search(/[\*\_\[\!`~<\$]/);
222
254
  if (nextSpecial === -1) {
223
255
  spans.push({
224
256
  type: "text",
@@ -244,6 +276,35 @@ function parseInlineSpans(text) {
244
276
  function slugify(text) {
245
277
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
246
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
+ }
247
308
  function parseMarkdownDocument(rawMarkdown) {
248
309
  const { data: frontmatter, content } = (0, import_gray_matter.default)(rawMarkdown);
249
310
  const metadata = frontmatter || {};
@@ -255,6 +316,7 @@ function parseMarkdownDocument(rawMarkdown) {
255
316
  const lines = cleanContent.split(/\r?\n/);
256
317
  const nodes = [];
257
318
  const tocEntries = [];
319
+ const footnoteDefs = [];
258
320
  let i = 0;
259
321
  while (i < lines.length) {
260
322
  const line = lines[i];
@@ -283,6 +345,29 @@ function parseMarkdownDocument(rawMarkdown) {
283
345
  i++;
284
346
  continue;
285
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
+ }
286
371
  const codeBlockMatch = line.match(/^```(\w+)?/);
287
372
  if (codeBlockMatch) {
288
373
  const language = (codeBlockMatch[1] || "text").trim().toLowerCase();
@@ -309,6 +394,100 @@ function parseMarkdownDocument(rawMarkdown) {
309
394
  }
310
395
  continue;
311
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
+ }
312
491
  const calloutMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i);
313
492
  if (calloutMatch) {
314
493
  const calloutType = calloutMatch[1].toUpperCase();
@@ -432,7 +611,7 @@ function parseMarkdownDocument(rawMarkdown) {
432
611
  continue;
433
612
  }
434
613
  const paraLines = [];
435
- 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+/)) {
436
615
  paraLines.push(lines[i]);
437
616
  i++;
438
617
  }
@@ -443,12 +622,19 @@ function parseMarkdownDocument(rawMarkdown) {
443
622
  inlines: parseInlineSpans(paraText)
444
623
  });
445
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
+ }
446
631
  return {
447
632
  metadata,
448
633
  content: cleanContent,
449
634
  nodes,
450
635
  tocEntries,
451
- inlinedStyles
636
+ inlinedStyles,
637
+ footnoteDefs
452
638
  };
453
639
  }
454
640
 
@@ -514,9 +700,14 @@ async function resolveImage(src, baseDir = process.cwd()) {
514
700
  memoryImageCache.set(cacheKey, resolved2);
515
701
  return resolved2;
516
702
  }
517
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
703
+ let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
518
704
  if (!fs.existsSync(localPath)) {
519
- return null;
705
+ const cwdPath = path.resolve(process.cwd(), src);
706
+ if (fs.existsSync(cwdPath)) {
707
+ localPath = cwdPath;
708
+ } else {
709
+ return null;
710
+ }
520
711
  }
521
712
  const buffer = fs.readFileSync(localPath);
522
713
  const mimeType = getMimeType(localPath);
@@ -921,6 +1112,8 @@ var path4 = __toESM(require("path"));
921
1112
  var os = __toESM(require("os"));
922
1113
  var import_node_url2 = require("url");
923
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");
924
1117
 
925
1118
  // src/core/html/htmlBuilder.ts
926
1119
  var fs3 = __toESM(require("fs"));
@@ -1163,6 +1356,12 @@ var DEFAULT_CONFIG = {
1163
1356
  },
1164
1357
  toc: false,
1165
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,
1166
1365
  embedImages: true,
1167
1366
  metadata: void 0,
1168
1367
  watch: false,
@@ -1319,7 +1518,8 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1319
1518
  return str;
1320
1519
  }
1321
1520
  function replaceDocumentTokens(template = "", meta) {
1322
- 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 || "");
1323
1523
  }
1324
1524
  function normalizeWatermark(rawWatermark) {
1325
1525
  if (!rawWatermark) {
@@ -1395,6 +1595,190 @@ function normalizeHeaderFooter(raw, meta) {
1395
1595
  dividerColor: raw.dividerColor || "#E2E8F0"
1396
1596
  };
1397
1597
  }
1598
+ function normalizeSignatures(raw, meta = {}) {
1599
+ if (!raw) return void 0;
1600
+ let rawItems = [];
1601
+ let rawConfig = {};
1602
+ if (Array.isArray(raw)) {
1603
+ rawItems = raw;
1604
+ } else if (typeof raw === "object" && Array.isArray(raw.items)) {
1605
+ rawItems = raw.items;
1606
+ rawConfig = raw;
1607
+ }
1608
+ if (rawItems.length === 0) return void 0;
1609
+ const cappedItems = rawItems.slice(0, 4);
1610
+ const items = cappedItems.map((item) => {
1611
+ const tokenCtx = meta;
1612
+ const rawName = typeof item.name === "string" ? item.name : "";
1613
+ const name = replaceDocumentTokens(rawName, tokenCtx).trim();
1614
+ const title = item.title ? replaceDocumentTokens(item.title, tokenCtx).trim() : void 0;
1615
+ const role = item.role ? replaceDocumentTokens(item.role, tokenCtx).trim() : void 0;
1616
+ let dateStr;
1617
+ if (typeof item.date === "string") {
1618
+ dateStr = replaceDocumentTokens(item.date, tokenCtx).trim();
1619
+ } else if (item.date === true) {
1620
+ dateStr = meta.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1621
+ }
1622
+ let signatureHeight = 60;
1623
+ if (typeof item.signatureHeight === "number") {
1624
+ signatureHeight = item.signatureHeight;
1625
+ } else if (typeof item.signatureHeight === "string") {
1626
+ const parsed = parseFloat(item.signatureHeight);
1627
+ if (!isNaN(parsed)) signatureHeight = parsed;
1628
+ }
1629
+ return {
1630
+ title,
1631
+ name: name || "Authorized Signatory",
1632
+ role,
1633
+ date: dateStr,
1634
+ image: item.image,
1635
+ signatureHeight
1636
+ };
1637
+ });
1638
+ const align = rawConfig.align || (items.length === 1 ? "right" : "space-between");
1639
+ const style = rawConfig.style || "line";
1640
+ const borderColor = rawConfig.borderColor || "#CBD5E1";
1641
+ const titleColor = rawConfig.titleColor || "#64748B";
1642
+ const nameColor = rawConfig.nameColor || "#0F172A";
1643
+ const roleColor = rawConfig.roleColor || "#64748B";
1644
+ const spacingBeforeRaw = rawConfig.spacingBefore ?? "2.5rem";
1645
+ const spacingBefore = formatMarginCss(spacingBeforeRaw, "2.5rem");
1646
+ const spacingBeforeTwip = parseMarginToTwip(spacingBeforeRaw, 600);
1647
+ return {
1648
+ items,
1649
+ align,
1650
+ style,
1651
+ borderColor,
1652
+ titleColor,
1653
+ nameColor,
1654
+ roleColor,
1655
+ spacingBefore,
1656
+ spacingBeforeTwip
1657
+ };
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
+ }
1398
1782
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1399
1783
  const configMeta = userConfig.metadata || {};
1400
1784
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1435,6 +1819,17 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1435
1819
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1436
1820
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1437
1821
  const watermark = normalizeWatermark(rawWatermark);
1822
+ const rawSignatures = mergedMeta.signatures || userConfig.signatures;
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;
1438
1833
  const cssList = [];
1439
1834
  const addCss = (item) => {
1440
1835
  if (!item) return;
@@ -1462,7 +1857,13 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1462
1857
  header,
1463
1858
  footer,
1464
1859
  toc,
1860
+ signatures,
1465
1861
  watermark,
1862
+ coverPage,
1863
+ backCover,
1864
+ numberHeadings,
1865
+ security,
1866
+ math,
1466
1867
  css: cssList,
1467
1868
  embedImages,
1468
1869
  bundleHtml,
@@ -1470,6 +1871,45 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1470
1871
  };
1471
1872
  }
1472
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
+
1473
1913
  // src/core/html/htmlBuilder.ts
1474
1914
  function escapeHtml(str) {
1475
1915
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -1512,6 +1952,15 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1512
1952
  result += `<code>${escapeHtml(span.content)}</code>`;
1513
1953
  continue;
1514
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
+ }
1515
1964
  if (span.type === "htmlInline") {
1516
1965
  result += span.content;
1517
1966
  continue;
@@ -1520,66 +1969,9 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1520
1969
  }
1521
1970
  return result;
1522
1971
  }
1523
- async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1524
- const resolved = resolveDocumentConfig(doc.metadata, config);
1525
- const baseThemeCss = generateThemeCss(resolved.theme);
1526
- let customCss = "";
1527
- for (const cssPath of resolved.css) {
1528
- const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
1529
- if (fs3.existsSync(fullCssPath)) {
1530
- customCss += `
1531
- /* Custom CSS: ${cssPath} */
1532
- ` + fs3.readFileSync(fullCssPath, "utf-8");
1533
- }
1534
- }
1535
- const inlinedCss = doc.inlinedStyles.join("\n");
1972
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1536
1973
  let bodyHtml = "";
1537
- if (resolved.title) {
1538
- bodyHtml += ` <header class="document-header">
1539
- `;
1540
- bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1541
- `;
1542
- if (resolved.subtitle) {
1543
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1544
- `;
1545
- }
1546
- if (resolved.author || resolved.date || resolved.version) {
1547
- bodyHtml += ` <div class="document-meta">
1548
- `;
1549
- if (resolved.author) {
1550
- bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1551
- `;
1552
- }
1553
- if (resolved.version) {
1554
- bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1555
- `;
1556
- }
1557
- if (resolved.date) {
1558
- bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1559
- `;
1560
- }
1561
- bodyHtml += ` </div>
1562
- `;
1563
- }
1564
- bodyHtml += ` </header>
1565
- `;
1566
- }
1567
- if (resolved.toc && doc.tocEntries.length > 0) {
1568
- bodyHtml += ` <nav class="table-of-contents">
1569
- `;
1570
- bodyHtml += ` <h2>Table of Contents</h2>
1571
- <ul>
1572
- `;
1573
- for (const entry of doc.tocEntries) {
1574
- const indent = " ".repeat(entry.level);
1575
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1576
- `;
1577
- }
1578
- bodyHtml += ` </ul>
1579
- </nav>
1580
- `;
1581
- }
1582
- for (const node of doc.nodes) {
1974
+ for (const node of nodes) {
1583
1975
  if (node.type === "heading") {
1584
1976
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1585
1977
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
@@ -1589,6 +1981,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1589
1981
  if (node.type === "paragraph") {
1590
1982
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1591
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>
1592
2004
  `;
1593
2005
  continue;
1594
2006
  }
@@ -1677,87 +2089,734 @@ ${escapeHtml(node.text || "")}
1677
2089
  continue;
1678
2090
  }
1679
2091
  }
1680
- let watermarkCss = "";
1681
- let watermarkHtml = "";
1682
- if (resolved.watermark) {
1683
- const wm = resolved.watermark;
1684
- watermarkCss = `
1685
- .document-watermark {
1686
- position: fixed;
1687
- top: 50%;
1688
- left: 50%;
1689
- transform: translate(-50%, -50%) rotate(${wm.rotate}deg);
1690
- font-size: ${wm.fontSize}pt;
1691
- font-weight: 900;
1692
- color: ${wm.color};
1693
- opacity: ${wm.opacity};
1694
- pointer-events: none;
1695
- z-index: 0;
1696
- user-select: none;
1697
- text-transform: uppercase;
1698
- letter-spacing: 0.15em;
1699
- white-space: nowrap;
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>`;
1700
2101
  }
1701
- .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;
1702
2122
  position: relative;
1703
- 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;
2201
+ }
2202
+ @media print {
2203
+ .markforge-cover {
2204
+ page-break-after: always;
2205
+ break-after: page;
2206
+ height: 100vh;
2207
+ min-height: 100vh;
2208
+ max-height: 100vh;
2209
+ box-sizing: border-box;
2210
+ overflow: hidden;
2211
+ margin: 0;
2212
+ -webkit-print-color-adjust: exact;
2213
+ print-color-adjust: exact;
2214
+ }
1704
2215
  }
1705
2216
  `;
1706
- watermarkHtml = ` <div class="document-watermark">${escapeHtml(wm.text)}</div>
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>
1707
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>`;
1708
2241
  }
1709
- const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
1710
- const mermaidScript = hasMermaid ? `<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
1711
- <script>
1712
- mermaid.initialize({
1713
- startOnLoad: true,
1714
- theme: 'neutral',
1715
- themeVariables: {
1716
- primaryColor: '#33CDCF',
1717
- primaryTextColor: '#0F172A',
1718
- primaryBorderColor: '#009DA0',
1719
- lineColor: '#009DA0',
1720
- secondaryColor: '#ECFDFD',
1721
- tertiaryColor: '#F8FAFC'
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>`);
1722
2255
  }
1723
- });
1724
- </script>` : "";
1725
- return `<!DOCTYPE html>
1726
- <html lang="${resolved.lang}">
1727
- <head>
1728
- <meta charset="UTF-8">
1729
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1730
- <title>${escapeHtml(resolved.title)}</title>
1731
- <style>
1732
- ${THEME_COMPONENTS}
1733
- ${baseThemeCss}
1734
- ${customCss}
1735
- ${inlinedCss}
1736
- ${watermarkCss}
1737
- </style>
1738
- </head>
1739
- <body>
1740
- ${watermarkHtml} <div class="document-container">
1741
- ${bodyHtml} </div>
1742
- ${mermaidScript}
1743
- </body>
1744
- </html>`;
1745
- }
1746
-
1747
- // src/core/pdf/pdfBuilder.ts
1748
- function findChromeExecutable() {
1749
- if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1750
- return process.env.CHROME_PATH;
2256
+ }
1751
2257
  }
1752
- if (process.env.PUPPETEER_EXECUTABLE_PATH && fs4.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
1753
- return process.env.PUPPETEER_EXECUTABLE_PATH;
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")};
1754
2275
  }
1755
- const isWin = process.platform === "win32";
1756
- const winLocalAppData = process.env.LOCALAPPDATA ?? "";
1757
- const winProgramFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
1758
- const winProgramFilesX86 = process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
1759
- const candidates = [
1760
- // Linux
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;" : ""}
2608
+ display: flex;
2609
+ flex-direction: column;
2610
+ ${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
2611
+ }
2612
+ .markforge-sig-title {
2613
+ font-size: 0.85rem;
2614
+ color: ${sig.titleColor};
2615
+ font-weight: 600;
2616
+ margin-bottom: 6px;
2617
+ }
2618
+ .markforge-sig-space {
2619
+ height: var(--sig-height, 60px);
2620
+ display: flex;
2621
+ align-items: center;
2622
+ justify-content: center;
2623
+ margin-bottom: 6px;
2624
+ }
2625
+ .markforge-sig-space img {
2626
+ max-height: 100%;
2627
+ max-width: 100%;
2628
+ object-fit: contain;
2629
+ }
2630
+ .markforge-sig-line {
2631
+ ${sig.style === "line" ? `border-bottom: 1.5px solid ${sig.borderColor}; margin-bottom: 8px;` : ""}
2632
+ }
2633
+ .markforge-sig-name {
2634
+ font-size: 0.95rem;
2635
+ font-weight: 700;
2636
+ color: ${sig.nameColor};
2637
+ }
2638
+ .markforge-sig-role {
2639
+ font-size: 0.82rem;
2640
+ color: ${sig.roleColor};
2641
+ margin-top: 2px;
2642
+ }
2643
+ .markforge-sig-date {
2644
+ font-size: 0.78rem;
2645
+ color: ${sig.roleColor};
2646
+ margin-top: 2px;
2647
+ }
2648
+ @media print {
2649
+ .markforge-signatures {
2650
+ page-break-inside: avoid;
2651
+ break-inside: avoid;
2652
+ }
2653
+ }
2654
+ `;
2655
+ const itemCards = sig.items.map((item) => {
2656
+ const titleHtml = item.title ? `<div class="markforge-sig-title">${escapeHtml(item.title)}</div>` : "";
2657
+ let signSpaceHtml = "";
2658
+ if (item.image) {
2659
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"><img src="${escapeHtml(item.image)}" alt="Signature" /></div>`;
2660
+ } else {
2661
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"></div>`;
2662
+ }
2663
+ const lineHtml = sig.style === "line" ? `<div class="markforge-sig-line"></div>` : "";
2664
+ const nameHtml = `<div class="markforge-sig-name">${escapeHtml(item.name)}</div>`;
2665
+ const roleHtml = item.role ? `<div class="markforge-sig-role">${escapeHtml(item.role)}</div>` : "";
2666
+ const dateHtml = item.date ? `<div class="markforge-sig-date">Date: ${escapeHtml(item.date)}</div>` : "";
2667
+ return ` <div class="markforge-signature-card">
2668
+ ${titleHtml}
2669
+ ${signSpaceHtml}
2670
+ ${lineHtml}
2671
+ ${nameHtml}
2672
+ ${roleHtml}
2673
+ ${dateHtml}
2674
+ </div>`;
2675
+ }).join("\n");
2676
+ signaturesHtml = `
2677
+ <div class="markforge-signatures">
2678
+ ${itemCards}
2679
+ </div>
2680
+ `;
2681
+ }
2682
+ const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
2683
+ const mermaidScript = hasMermaid ? `<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
2684
+ <script>
2685
+ mermaid.initialize({
2686
+ startOnLoad: true,
2687
+ theme: 'neutral',
2688
+ themeVariables: {
2689
+ primaryColor: '#33CDCF',
2690
+ primaryTextColor: '#0F172A',
2691
+ primaryBorderColor: '#009DA0',
2692
+ lineColor: '#009DA0',
2693
+ secondaryColor: '#ECFDFD',
2694
+ tertiaryColor: '#F8FAFC'
2695
+ }
2696
+ });
2697
+ </script>` : "";
2698
+ return `<!DOCTYPE html>
2699
+ <html lang="${resolved.lang}">
2700
+ <head>
2701
+ <meta charset="UTF-8">
2702
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2703
+ <title>${escapeHtml(resolved.title)}</title>
2704
+ <style>
2705
+ ${THEME_COMPONENTS}
2706
+ ${baseThemeCss}
2707
+ ${KATEX_INLINE_CSS}
2708
+ ${extraCss}
2709
+ ${coverCss}
2710
+ ${backCss}
2711
+ ${customCss}
2712
+ ${inlinedCss}
2713
+ ${watermarkCss}
2714
+ ${signaturesCss}
2715
+ </style>
2716
+ </head>
2717
+ <body>
2718
+ ${watermarkHtml}${coverHtml} <div class="document-container">
2719
+ ${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
2720
+ ${mermaidScript}
2721
+ ${backHtml}</body>
2722
+ </html>`;
2723
+ }
2724
+
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
+ }
2807
+ function findChromeExecutable() {
2808
+ if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
2809
+ return process.env.CHROME_PATH;
2810
+ }
2811
+ if (process.env.PUPPETEER_EXECUTABLE_PATH && fs4.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
2812
+ return process.env.PUPPETEER_EXECUTABLE_PATH;
2813
+ }
2814
+ const isWin = process.platform === "win32";
2815
+ const winLocalAppData = process.env.LOCALAPPDATA ?? "";
2816
+ const winProgramFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
2817
+ const winProgramFilesX86 = process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
2818
+ const candidates = [
2819
+ // Linux
1761
2820
  "/usr/bin/google-chrome",
1762
2821
  "/usr/bin/google-chrome-stable",
1763
2822
  "/usr/bin/chromium",
@@ -1809,7 +2868,7 @@ function findChromeExecutable() {
1809
2868
  return null;
1810
2869
  }
1811
2870
  function injectPagedMediaStyles(html, config, metadata) {
1812
- var _a, _b, _c, _d, _e, _f;
2871
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1813
2872
  const resolved = resolveDocumentConfig(metadata || {}, config);
1814
2873
  const size = resolved.paperSize;
1815
2874
  const orientation = resolved.orientation;
@@ -1853,6 +2912,38 @@ function injectPagedMediaStyles(html, config, metadata) {
1853
2912
  ${fontStyle}
1854
2913
  }`;
1855
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
+ }` : "";
1856
2947
  const pagedCss = `
1857
2948
  @page {
1858
2949
  size: ${size} ${orientation};
@@ -1860,15 +2951,18 @@ function injectPagedMediaStyles(html, config, metadata) {
1860
2951
  margin-bottom: ${bottom};
1861
2952
  margin-left: ${left};
1862
2953
  margin-right: ${right};
1863
- ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
1864
- ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
1865
- ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
1866
- ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
1867
- ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
1868
- ${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)}
1869
2960
  }
2961
+ ${coverPageCss}
2962
+ ${backCoverCss}
1870
2963
  @media print {
1871
2964
  body { padding: 0; }
2965
+ .document-watermark { display: none !important; }
1872
2966
  h1, h2, h3, pre, table, blockquote, .callout {
1873
2967
  break-inside: avoid;
1874
2968
  }
@@ -1916,6 +3010,7 @@ startxref
1916
3010
  return Buffer.from(pdfBody, "utf-8");
1917
3011
  }
1918
3012
  async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
3013
+ var _a, _b, _c;
1919
3014
  const baseHtml = await buildHtmlDocument(doc, config, baseDir);
1920
3015
  const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
1921
3016
  const chromePath = findChromeExecutable();
@@ -1925,6 +3020,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1925
3020
  const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
1926
3021
  const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
1927
3022
  const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
3023
+ const isWin = process.platform === "win32";
1928
3024
  const isolatedFlags = [
1929
3025
  `--user-data-dir=${tmpProfile}`,
1930
3026
  "--no-first-run",
@@ -1935,7 +3031,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1935
3031
  "--disable-default-apps",
1936
3032
  "--disable-extensions",
1937
3033
  "--disable-domain-reliability",
1938
- "--disable-client-side-phishing-detection",
1939
3034
  "--disable-breakpad",
1940
3035
  "--disable-component-extensions-with-background-pages",
1941
3036
  "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
@@ -1944,12 +3039,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1944
3039
  "--mute-audio",
1945
3040
  "--no-service-autorun",
1946
3041
  "--disable-gpu",
1947
- "--no-sandbox",
1948
- "--disable-setuid-sandbox",
1949
- "--allow-file-access-from-files",
1950
- "--disable-web-security",
3042
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
1951
3043
  "--force-color-profile=srgb",
1952
- "--no-pdf-header-footer"
3044
+ "--no-pdf-header-footer",
3045
+ "--window-size=1200,1600"
1953
3046
  ];
1954
3047
  try {
1955
3048
  fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
@@ -1964,7 +3057,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1964
3057
  `--print-to-pdf=${tmpPdf}`,
1965
3058
  fileUrl
1966
3059
  ],
1967
- { timeout: 3e4 }
3060
+ { timeout: 3e4, windowsHide: true }
1968
3061
  );
1969
3062
  if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
1970
3063
  res = (0, import_node_child_process.spawnSync)(
@@ -1975,12 +3068,78 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1975
3068
  `--print-to-pdf=${tmpPdf}`,
1976
3069
  fileUrl
1977
3070
  ],
1978
- { timeout: 3e4 }
3071
+ { timeout: 3e4, windowsHide: true }
1979
3072
  );
1980
3073
  }
1981
3074
  if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
1982
3075
  const pdfBuffer = fs4.readFileSync(tmpPdf);
1983
- 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
+ }
1984
3143
  }
1985
3144
  } catch {
1986
3145
  } finally {
@@ -2235,6 +3394,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2235
3394
  );
2236
3395
  continue;
2237
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
+ }
2238
3420
  runs.push(
2239
3421
  new import_docx.TextRun({
2240
3422
  text: span.content,
@@ -2249,7 +3431,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2249
3431
  return runs;
2250
3432
  }
2251
3433
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2252
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3434
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2253
3435
  const resolved = resolveDocumentConfig(doc.metadata, config);
2254
3436
  const docElements = [];
2255
3437
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -2260,7 +3442,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2260
3442
  const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2261
3443
  const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2262
3444
  const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2263
- if (resolved.title) {
3445
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2264
3446
  docElements.push(
2265
3447
  new import_docx.Paragraph({
2266
3448
  children: [
@@ -2676,7 +3858,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2676
3858
  }
2677
3859
  if (node.type === "table" && node.children) {
2678
3860
  const tableRows = [];
2679
- 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;
2680
3862
  const colWidth = Math.floor(9e3 / numCols);
2681
3863
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2682
3864
  const rowNode = node.children[rowIdx];
@@ -2686,7 +3868,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2686
3868
  if (rowNode.children) {
2687
3869
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2688
3870
  const cellNode = rowNode.children[colIdx];
2689
- const align = (_c = node.align) == null ? void 0 : _c[colIdx];
3871
+ const align = (_d = node.align) == null ? void 0 : _d[colIdx];
2690
3872
  let alignment = import_docx.AlignmentType.LEFT;
2691
3873
  if (align === "center") alignment = import_docx.AlignmentType.CENTER;
2692
3874
  if (align === "right") alignment = import_docx.AlignmentType.RIGHT;
@@ -2825,15 +4007,175 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2825
4007
  }
2826
4008
  continue;
2827
4009
  }
2828
- }
2829
- const contentWidthTwip = Math.max(
2830
- 1e3,
2831
- resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2832
- );
2833
- const centerPos = Math.round(contentWidthTwip / 2);
2834
- const rightPos = contentWidthTwip;
2835
- const headerRuns = [];
2836
- if ((_d = resolved.header) == null ? void 0 : _d.left) {
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,
4123
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4124
+ );
4125
+ docElements.push(new import_docx.Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
4126
+ const sigCells = [];
4127
+ const colWidths = [];
4128
+ if (numItems === 1) {
4129
+ const cardWidth = Math.min(3400, Math.floor(contentWidth * 0.42));
4130
+ const spacerWidth = contentWidth - cardWidth;
4131
+ const cardCell = await buildDocxSignatureCell(sig.items[0], sig, cardWidth, defaultFont, baseDir);
4132
+ if (sig.align === "left") {
4133
+ colWidths.push(cardWidth, spacerWidth);
4134
+ sigCells.push(cardCell, createEmptyDocxCell(spacerWidth));
4135
+ } else if (sig.align === "center") {
4136
+ const sideWidth = Math.floor(spacerWidth / 2);
4137
+ colWidths.push(sideWidth, cardWidth, sideWidth);
4138
+ sigCells.push(createEmptyDocxCell(sideWidth), cardCell, createEmptyDocxCell(sideWidth));
4139
+ } else {
4140
+ colWidths.push(spacerWidth, cardWidth);
4141
+ sigCells.push(createEmptyDocxCell(spacerWidth), cardCell);
4142
+ }
4143
+ } else {
4144
+ const colWidth = Math.floor(contentWidth / numItems);
4145
+ for (let i = 0; i < numItems; i++) {
4146
+ colWidths.push(colWidth);
4147
+ const cell = await buildDocxSignatureCell(sig.items[i], sig, colWidth, defaultFont, baseDir);
4148
+ sigCells.push(cell);
4149
+ }
4150
+ }
4151
+ const sigTable = new import_docx.Table({
4152
+ width: { size: 100, type: import_docx.WidthType.PERCENTAGE },
4153
+ columnWidths: colWidths,
4154
+ borders: {
4155
+ top: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4156
+ bottom: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4157
+ left: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4158
+ right: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4159
+ insideHorizontal: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4160
+ insideVertical: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" }
4161
+ },
4162
+ rows: [
4163
+ new import_docx.TableRow({
4164
+ cantSplit: true,
4165
+ children: sigCells
4166
+ })
4167
+ ]
4168
+ });
4169
+ docElements.push(sigTable);
4170
+ }
4171
+ const contentWidthTwip = Math.max(
4172
+ 1e3,
4173
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4174
+ );
4175
+ const centerPos = Math.round(contentWidthTwip / 2);
4176
+ const rightPos = contentWidthTwip;
4177
+ const headerRuns = [];
4178
+ if ((_e = resolved.header) == null ? void 0 : _e.left) {
2837
4179
  headerRuns.push(
2838
4180
  new import_docx.TextRun({
2839
4181
  text: resolved.header.left.text,
@@ -2846,7 +4188,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2846
4188
  );
2847
4189
  }
2848
4190
  headerRuns.push(new import_docx.TextRun({ text: " " }));
2849
- if ((_e = resolved.header) == null ? void 0 : _e.center) {
4191
+ if ((_f = resolved.header) == null ? void 0 : _f.center) {
2850
4192
  headerRuns.push(
2851
4193
  new import_docx.TextRun({
2852
4194
  text: resolved.header.center.text,
@@ -2859,7 +4201,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2859
4201
  );
2860
4202
  }
2861
4203
  headerRuns.push(new import_docx.TextRun({ text: " " }));
2862
- if ((_f = resolved.header) == null ? void 0 : _f.right) {
4204
+ if ((_g = resolved.header) == null ? void 0 : _g.right) {
2863
4205
  headerRuns.push(
2864
4206
  new import_docx.TextRun({
2865
4207
  text: resolved.header.right.text,
@@ -2898,7 +4240,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2898
4240
  ]
2899
4241
  }) : void 0;
2900
4242
  const footerRuns = [];
2901
- if ((_g = resolved.footer) == null ? void 0 : _g.left) {
4243
+ if ((_h = resolved.footer) == null ? void 0 : _h.left) {
2902
4244
  footerRuns.push(
2903
4245
  new import_docx.TextRun({
2904
4246
  text: resolved.footer.left.text,
@@ -2911,7 +4253,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2911
4253
  );
2912
4254
  }
2913
4255
  footerRuns.push(new import_docx.TextRun({ text: " " }));
2914
- if ((_h = resolved.footer) == null ? void 0 : _h.center) {
4256
+ if ((_i = resolved.footer) == null ? void 0 : _i.center) {
2915
4257
  footerRuns.push(
2916
4258
  new import_docx.TextRun({
2917
4259
  text: resolved.footer.center.text,
@@ -2924,7 +4266,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2924
4266
  );
2925
4267
  }
2926
4268
  footerRuns.push(new import_docx.TextRun({ text: " " }));
2927
- if ((_i = resolved.footer) == null ? void 0 : _i.right) {
4269
+ if ((_j = resolved.footer) == null ? void 0 : _j.right) {
2928
4270
  const rZone = resolved.footer.right;
2929
4271
  const rColor = rZone.color.replace("#", "");
2930
4272
  const rSize = (rZone.fontSize || 9) * 2;
@@ -3009,6 +4351,91 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3009
4351
  ]
3010
4352
  }) : void 0;
3011
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
+ }
3012
4439
  const document = new import_docx.Document({
3013
4440
  styles: {
3014
4441
  default: {
@@ -3029,32 +4456,383 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3029
4456
  }
3030
4457
  }
3031
4458
  },
3032
- sections: [
3033
- {
3034
- properties: {
3035
- page: {
3036
- size: {
3037
- width: resolved.paperDimensions.widthTwip,
3038
- height: resolved.paperDimensions.heightTwip,
3039
- orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
3040
- },
3041
- margin: {
3042
- top: resolved.margins.topTwip,
3043
- bottom: resolved.margins.bottomTwip,
3044
- left: resolved.margins.leftTwip,
3045
- right: resolved.margins.rightTwip,
3046
- header: 720,
3047
- footer: 720
3048
- }
4459
+ sections: docSections
4460
+ });
4461
+ return await import_docx.Packer.toBuffer(document);
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
+ }
4703
+ async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
4704
+ const cellParagraphs = [];
4705
+ if (item.title) {
4706
+ cellParagraphs.push(
4707
+ new import_docx.Paragraph({
4708
+ children: [
4709
+ new import_docx.TextRun({
4710
+ text: item.title,
4711
+ font: defaultFont,
4712
+ size: 18,
4713
+ // 9pt
4714
+ color: sig.titleColor.replace("#", ""),
4715
+ bold: true
4716
+ })
4717
+ ],
4718
+ spacing: { after: 60 }
4719
+ })
4720
+ );
4721
+ }
4722
+ if (item.image) {
4723
+ const resolvedImg = await resolveImage(item.image, baseDir);
4724
+ if (resolvedImg) {
4725
+ cellParagraphs.push(
4726
+ new import_docx.Paragraph({
4727
+ children: [
4728
+ new import_docx.ImageRun({
4729
+ data: resolvedImg.buffer,
4730
+ transformation: {
4731
+ width: 140,
4732
+ height: 60
4733
+ },
4734
+ type: "png"
4735
+ })
4736
+ ],
4737
+ spacing: { before: 40, after: 40 }
4738
+ })
4739
+ );
4740
+ } else {
4741
+ cellParagraphs.push(new import_docx.Paragraph({ spacing: { before: 240, after: 240 } }));
4742
+ }
4743
+ } else {
4744
+ cellParagraphs.push(new import_docx.Paragraph({ spacing: { before: 240, after: 240 } }));
4745
+ }
4746
+ if (sig.style === "line") {
4747
+ cellParagraphs.push(
4748
+ new import_docx.Paragraph({
4749
+ border: {
4750
+ bottom: {
4751
+ style: import_docx.BorderStyle.SINGLE,
4752
+ size: 6,
4753
+ space: 2,
4754
+ color: sig.borderColor.replace("#", "")
3049
4755
  }
3050
4756
  },
3051
- headers: docHeader ? { default: docHeader } : void 0,
3052
- footers: docFooter ? { default: docFooter } : void 0,
3053
- children: docElements
3054
- }
3055
- ]
4757
+ spacing: { after: 60 }
4758
+ })
4759
+ );
4760
+ }
4761
+ cellParagraphs.push(
4762
+ new import_docx.Paragraph({
4763
+ children: [
4764
+ new import_docx.TextRun({
4765
+ text: item.name,
4766
+ font: defaultFont,
4767
+ size: 21,
4768
+ // 10.5pt
4769
+ bold: true,
4770
+ color: sig.nameColor.replace("#", "")
4771
+ })
4772
+ ],
4773
+ spacing: { before: sig.style === "line" ? 40 : 20, after: 20 }
4774
+ })
4775
+ );
4776
+ if (item.role) {
4777
+ cellParagraphs.push(
4778
+ new import_docx.Paragraph({
4779
+ children: [
4780
+ new import_docx.TextRun({
4781
+ text: item.role,
4782
+ font: defaultFont,
4783
+ size: 18,
4784
+ // 9pt
4785
+ color: sig.roleColor.replace("#", "")
4786
+ })
4787
+ ],
4788
+ spacing: { after: 20 }
4789
+ })
4790
+ );
4791
+ }
4792
+ if (item.date) {
4793
+ cellParagraphs.push(
4794
+ new import_docx.Paragraph({
4795
+ children: [
4796
+ new import_docx.TextRun({
4797
+ text: `Date: ${item.date}`,
4798
+ font: defaultFont,
4799
+ size: 17,
4800
+ // 8.5pt
4801
+ color: sig.roleColor.replace("#", "")
4802
+ })
4803
+ ],
4804
+ spacing: { after: 20 }
4805
+ })
4806
+ );
4807
+ }
4808
+ const isBox = sig.style === "box";
4809
+ const boxBorder = { style: import_docx.BorderStyle.SINGLE, size: 4, color: sig.borderColor.replace("#", "") };
4810
+ const noneBorder = { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" };
4811
+ return new import_docx.TableCell({
4812
+ width: { size: widthDxa, type: import_docx.WidthType.DXA },
4813
+ shading: isBox ? { fill: "F8FAFC", type: import_docx.ShadingType.CLEAR } : void 0,
4814
+ margins: isBox ? { top: 140, bottom: 140, left: 160, right: 160 } : { top: 60, bottom: 60, left: 60, right: 60 },
4815
+ borders: {
4816
+ top: isBox ? boxBorder : noneBorder,
4817
+ bottom: isBox ? boxBorder : noneBorder,
4818
+ left: isBox ? boxBorder : noneBorder,
4819
+ right: isBox ? boxBorder : noneBorder
4820
+ },
4821
+ children: cellParagraphs
4822
+ });
4823
+ }
4824
+ function createEmptyDocxCell(widthDxa) {
4825
+ const noneBorder = { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" };
4826
+ return new import_docx.TableCell({
4827
+ width: { size: widthDxa, type: import_docx.WidthType.DXA },
4828
+ borders: {
4829
+ top: noneBorder,
4830
+ bottom: noneBorder,
4831
+ left: noneBorder,
4832
+ right: noneBorder
4833
+ },
4834
+ children: [new import_docx.Paragraph({})]
3056
4835
  });
3057
- return await import_docx.Packer.toBuffer(document);
3058
4836
  }
3059
4837
 
3060
4838
  // src/core/engine.ts
@@ -3149,14 +4927,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
3149
4927
  };
3150
4928
  }
3151
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
+
3152
5801
  // src/config/defineConfig.ts
3153
5802
  function defineConfig(config) {
3154
5803
  return config;
3155
5804
  }
3156
5805
 
3157
5806
  // src/version.ts
3158
- var fs7 = __toESM(require("fs"));
3159
- var path7 = __toESM(require("path"));
5807
+ var fs8 = __toESM(require("fs"));
5808
+ var path8 = __toESM(require("path"));
3160
5809
  var import_node_url4 = require("url");
3161
5810
  var import_meta = {};
3162
5811
  try {
@@ -3179,21 +5828,21 @@ try {
3179
5828
  }
3180
5829
  } catch {
3181
5830
  }
3182
- var FALLBACK_VERSION = "0.2.2";
5831
+ var FALLBACK_VERSION = "0.4.0";
3183
5832
  function readVersionFromPackageJson(fromDir) {
3184
5833
  let currentDir = fromDir;
3185
5834
  for (let i = 0; i < 6; i++) {
3186
5835
  try {
3187
- const pkgJsonPath = path7.join(currentDir, "package.json");
3188
- if (fs7.existsSync(pkgJsonPath)) {
3189
- 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"));
3190
5839
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
3191
5840
  return pkg.version;
3192
5841
  }
3193
5842
  }
3194
5843
  } catch {
3195
5844
  }
3196
- const parentDir = path7.dirname(currentDir);
5845
+ const parentDir = path8.dirname(currentDir);
3197
5846
  if (parentDir === currentDir) break;
3198
5847
  currentDir = parentDir;
3199
5848
  }
@@ -3204,7 +5853,7 @@ function getPackageDir() {
3204
5853
  return __dirname;
3205
5854
  }
3206
5855
  try {
3207
- return path7.dirname((0, import_node_url4.fileURLToPath)(import_meta.url));
5856
+ return path8.dirname((0, import_node_url4.fileURLToPath)(import_meta.url));
3208
5857
  } catch {
3209
5858
  return process.cwd();
3210
5859
  }
@@ -3216,6 +5865,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3216
5865
  // Annotate the CommonJS export names for ESM import in node:
3217
5866
  0 && (module.exports = {
3218
5867
  DEFAULT_CONFIG,
5868
+ KATEX_INLINE_CSS,
3219
5869
  MARKFORGE_VERSION,
3220
5870
  Orientation,
3221
5871
  OutputFormat,
@@ -3244,17 +5894,28 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3244
5894
  inlineHtmlImages,
3245
5895
  loadConfig,
3246
5896
  markforge,
5897
+ normalizeBackCover,
5898
+ normalizeCoverPage,
3247
5899
  normalizeHeaderFooter,
3248
5900
  normalizeHeaderFooterSlot,
5901
+ normalizeNumberHeadings,
5902
+ normalizeSecurity,
5903
+ normalizeSignatures,
3249
5904
  normalizeWatermark,
3250
5905
  parseInlineSpans,
3251
5906
  parseMarginToTwip,
5907
+ parseMarkdown,
3252
5908
  parseMarkdownDocument,
5909
+ renderBackCoverHtml,
5910
+ renderCoverPageHtml,
3253
5911
  renderInlinesToHtml,
5912
+ renderMathToHtml,
3254
5913
  renderMermaidToPng,
5914
+ renderNodesToHtml,
3255
5915
  replaceDocumentTokens,
3256
5916
  resolveDocumentConfig,
3257
5917
  resolveImage,
3258
5918
  slugify,
5919
+ startPreviewServer,
3259
5920
  tokenizeCodeLine
3260
5921
  });