@masumdev/markforge 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -49,6 +49,26 @@ function parseInlineSpans(text) {
49
49
  remaining = remaining.slice(imgMatch[0].length);
50
50
  continue;
51
51
  }
52
+ const fnMatch = remaining.match(/^\[\^([\w-]+)\]/);
53
+ if (fnMatch) {
54
+ const fnId = fnMatch[1];
55
+ spans.push({
56
+ type: "footnoteRef",
57
+ content: fnId,
58
+ footnoteId: fnId
59
+ });
60
+ remaining = remaining.slice(fnMatch[0].length);
61
+ continue;
62
+ }
63
+ const mathMatch = remaining.match(/^\$([^$\n]+?)\$/);
64
+ if (mathMatch && !mathMatch[1].startsWith("$")) {
65
+ spans.push({
66
+ type: "mathInline",
67
+ content: mathMatch[1]
68
+ });
69
+ remaining = remaining.slice(mathMatch[0].length);
70
+ continue;
71
+ }
52
72
  const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
53
73
  if (linkMatch) {
54
74
  spans.push({
@@ -148,7 +168,7 @@ function parseInlineSpans(text) {
148
168
  remaining = remaining.slice(fullTag.length);
149
169
  continue;
150
170
  }
151
- const nextSpecial = remaining.search(/[\*\_\[\!`~<]/);
171
+ const nextSpecial = remaining.search(/[\*\_\[\!`~<\$]/);
152
172
  if (nextSpecial === -1) {
153
173
  spans.push({
154
174
  type: "text",
@@ -174,6 +194,35 @@ function parseInlineSpans(text) {
174
194
  function slugify(text) {
175
195
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
176
196
  }
197
+ function applyHeadingNumbering(nodes, tocEntries, options) {
198
+ const depth = options.depth ?? 3;
199
+ const skipH1 = options.skipH1 ?? false;
200
+ const prefix = options.prefix ?? "";
201
+ const counters = [0, 0, 0, 0, 0, 0];
202
+ for (const node of nodes) {
203
+ if (node.type === "heading" && node.level) {
204
+ const lvl = node.level;
205
+ if (lvl > depth) continue;
206
+ if (lvl === 1 && skipH1) continue;
207
+ const idx = lvl - 1;
208
+ counters[idx]++;
209
+ for (let c = idx + 1; c < counters.length; c++) {
210
+ counters[c] = 0;
211
+ }
212
+ const startIdx = skipH1 ? 1 : 0;
213
+ const parts = counters.slice(startIdx, idx + 1).filter((n) => n > 0);
214
+ const numberStr = parts.join(".") + ".";
215
+ const fullPrefix = prefix ? `${prefix} ${numberStr} ` : `${numberStr} `;
216
+ const originalText = node.text || "";
217
+ node.text = fullPrefix + originalText;
218
+ node.inlines = parseInlineSpans(node.text);
219
+ const toc = tocEntries.find((t) => t.id === node.id);
220
+ if (toc) {
221
+ toc.text = node.text;
222
+ }
223
+ }
224
+ }
225
+ }
177
226
  function parseMarkdownDocument(rawMarkdown) {
178
227
  const { data: frontmatter, content } = matter(rawMarkdown);
179
228
  const metadata = frontmatter || {};
@@ -185,6 +234,7 @@ function parseMarkdownDocument(rawMarkdown) {
185
234
  const lines = cleanContent.split(/\r?\n/);
186
235
  const nodes = [];
187
236
  const tocEntries = [];
237
+ const footnoteDefs = [];
188
238
  let i = 0;
189
239
  while (i < lines.length) {
190
240
  const line = lines[i];
@@ -213,6 +263,29 @@ function parseMarkdownDocument(rawMarkdown) {
213
263
  i++;
214
264
  continue;
215
265
  }
266
+ if (line.trim().startsWith("$$")) {
267
+ const mathLines = [];
268
+ const singleLine = line.trim().match(/^\$\$(.+)\$\$$/);
269
+ if (singleLine) {
270
+ nodes.push({
271
+ type: "mathBlock",
272
+ text: singleLine[1].trim()
273
+ });
274
+ i++;
275
+ continue;
276
+ }
277
+ i++;
278
+ while (i < lines.length && !lines[i].trim().startsWith("$$")) {
279
+ mathLines.push(lines[i]);
280
+ i++;
281
+ }
282
+ if (i < lines.length) i++;
283
+ nodes.push({
284
+ type: "mathBlock",
285
+ text: mathLines.join("\n").trim()
286
+ });
287
+ continue;
288
+ }
216
289
  const codeBlockMatch = line.match(/^```(\w+)?/);
217
290
  if (codeBlockMatch) {
218
291
  const language = (codeBlockMatch[1] || "text").trim().toLowerCase();
@@ -239,6 +312,100 @@ function parseMarkdownDocument(rawMarkdown) {
239
312
  }
240
313
  continue;
241
314
  }
315
+ const colsMatch = line.trim().match(/^:::columns(?:\s+\[?([\w\s=.-]+)\]?)?$/i);
316
+ if (colsMatch) {
317
+ const attrStr = colsMatch[1] || "";
318
+ let colsCount = 2;
319
+ let colGap = "1.5rem";
320
+ if (attrStr) {
321
+ const numMatch = attrStr.trim().match(/^(\d+)$/);
322
+ const cMatch = attrStr.match(/cols=(\d+)/i) || attrStr.match(/columns=(\d+)/i);
323
+ const gMatch = attrStr.match(/gap=([^\s]+)/i);
324
+ if (numMatch) colsCount = parseInt(numMatch[1], 10);
325
+ else if (cMatch) colsCount = parseInt(cMatch[1], 10);
326
+ if (gMatch) colGap = gMatch[1];
327
+ }
328
+ const columnNodes = [];
329
+ let currentColumnLines = [];
330
+ let inColBlock = false;
331
+ i++;
332
+ while (i < lines.length) {
333
+ const curLine = lines[i];
334
+ const trimmed = curLine.trim();
335
+ if (/^:::col(?:umn)?$/i.test(trimmed)) {
336
+ if (currentColumnLines.length > 0) {
337
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
338
+ columnNodes.push({
339
+ type: "column",
340
+ children: subDoc.nodes
341
+ });
342
+ currentColumnLines = [];
343
+ }
344
+ inColBlock = true;
345
+ i++;
346
+ } else if (trimmed === ":::") {
347
+ if (inColBlock) {
348
+ if (currentColumnLines.length > 0) {
349
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
350
+ columnNodes.push({
351
+ type: "column",
352
+ children: subDoc.nodes
353
+ });
354
+ currentColumnLines = [];
355
+ }
356
+ inColBlock = false;
357
+ i++;
358
+ } else {
359
+ if (currentColumnLines.length > 0) {
360
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
361
+ columnNodes.push({
362
+ type: "column",
363
+ children: subDoc.nodes
364
+ });
365
+ currentColumnLines = [];
366
+ }
367
+ i++;
368
+ break;
369
+ }
370
+ } else {
371
+ currentColumnLines.push(curLine);
372
+ i++;
373
+ }
374
+ }
375
+ if (currentColumnLines.length > 0) {
376
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
377
+ columnNodes.push({
378
+ type: "column",
379
+ children: subDoc.nodes
380
+ });
381
+ }
382
+ nodes.push({
383
+ type: "columns",
384
+ columnsCount: columnNodes.length > 0 ? columnNodes.length : colsCount,
385
+ columnGap: colGap,
386
+ children: columnNodes
387
+ });
388
+ continue;
389
+ }
390
+ const fnDefMatch = line.match(/^\[\^([\w-]+)\]:\s+(.+)$/);
391
+ if (fnDefMatch) {
392
+ const fnId = fnDefMatch[1];
393
+ const fnText = fnDefMatch[2].trim();
394
+ const inlines = parseInlineSpans(fnText);
395
+ footnoteDefs.push({
396
+ id: fnId,
397
+ text: fnText,
398
+ inlines
399
+ });
400
+ nodes.push({
401
+ type: "footnoteDef",
402
+ footnoteId: fnId,
403
+ text: fnText,
404
+ inlines
405
+ });
406
+ i++;
407
+ continue;
408
+ }
242
409
  const calloutMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i);
243
410
  if (calloutMatch) {
244
411
  const calloutType = calloutMatch[1].toUpperCase();
@@ -362,7 +529,7 @@ function parseMarkdownDocument(rawMarkdown) {
362
529
  continue;
363
530
  }
364
531
  const paraLines = [];
365
- 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+/)) {
532
+ 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+/)) {
366
533
  paraLines.push(lines[i]);
367
534
  i++;
368
535
  }
@@ -373,12 +540,19 @@ function parseMarkdownDocument(rawMarkdown) {
373
540
  inlines: parseInlineSpans(paraText)
374
541
  });
375
542
  }
543
+ if (metadata.numberHeadings) {
544
+ const opts = typeof metadata.numberHeadings === "boolean" ? { enabled: metadata.numberHeadings } : metadata.numberHeadings;
545
+ if (opts.enabled !== false) {
546
+ applyHeadingNumbering(nodes, tocEntries, opts);
547
+ }
548
+ }
376
549
  return {
377
550
  metadata,
378
551
  content: cleanContent,
379
552
  nodes,
380
553
  tocEntries,
381
- inlinedStyles
554
+ inlinedStyles,
555
+ footnoteDefs
382
556
  };
383
557
  }
384
558
 
@@ -466,9 +640,14 @@ async function resolveImage(src, baseDir = process.cwd()) {
466
640
  memoryImageCache.set(cacheKey, resolved2);
467
641
  return resolved2;
468
642
  }
469
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
643
+ let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
470
644
  if (!fs.existsSync(localPath)) {
471
- return null;
645
+ const cwdPath = path.resolve(process.cwd(), src);
646
+ if (fs.existsSync(cwdPath)) {
647
+ localPath = cwdPath;
648
+ } else {
649
+ return null;
650
+ }
472
651
  }
473
652
  const buffer = fs.readFileSync(localPath);
474
653
  const mimeType = getMimeType(localPath);
@@ -873,6 +1052,8 @@ import * as path4 from "path";
873
1052
  import * as os from "os";
874
1053
  import { pathToFileURL as pathToFileURL2 } from "url";
875
1054
  import { spawnSync } from "child_process";
1055
+ import { PDFDocument } from "pdf-lib";
1056
+ import { encryptPDF } from "@pdfsmaller/pdf-encrypt";
876
1057
 
877
1058
  // src/core/html/htmlBuilder.ts
878
1059
  import * as fs3 from "fs";
@@ -1115,6 +1296,12 @@ var DEFAULT_CONFIG = {
1115
1296
  },
1116
1297
  toc: false,
1117
1298
  watermark: void 0,
1299
+ signatures: void 0,
1300
+ coverPage: void 0,
1301
+ backCover: void 0,
1302
+ numberHeadings: void 0,
1303
+ security: void 0,
1304
+ math: true,
1118
1305
  embedImages: true,
1119
1306
  metadata: void 0,
1120
1307
  watch: false,
@@ -1271,7 +1458,8 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1271
1458
  return str;
1272
1459
  }
1273
1460
  function replaceDocumentTokens(template = "", meta) {
1274
- 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 || "");
1461
+ const currentYear = meta.year || (/* @__PURE__ */ new Date()).getFullYear().toString();
1462
+ 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 || "");
1275
1463
  }
1276
1464
  function normalizeWatermark(rawWatermark) {
1277
1465
  if (!rawWatermark) {
@@ -1408,6 +1596,129 @@ function normalizeSignatures(raw, meta = {}) {
1408
1596
  spacingBeforeTwip
1409
1597
  };
1410
1598
  }
1599
+ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1600
+ if (!rawCover) return void 0;
1601
+ const cfg = typeof rawCover === "object" ? rawCover : {};
1602
+ if (cfg.enabled === false) return void 0;
1603
+ const preset = cfg.preset || "modern";
1604
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title || "Document Title";
1605
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle;
1606
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author;
1607
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1608
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version;
1609
+ let dateStr;
1610
+ if (typeof cfg.date === "string") {
1611
+ dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
1612
+ } else if (cfg.date === true) {
1613
+ dateStr = tokenCtx.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1614
+ } else {
1615
+ dateStr = tokenCtx.date;
1616
+ }
1617
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1618
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1619
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1620
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1621
+ const logoWidth = cfg.logoWidth;
1622
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1623
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1624
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1625
+ return {
1626
+ enabled: true,
1627
+ preset,
1628
+ title,
1629
+ subtitle,
1630
+ author,
1631
+ company,
1632
+ version,
1633
+ date: dateStr,
1634
+ badge,
1635
+ badgeColor,
1636
+ badgeTextColor,
1637
+ logo,
1638
+ logoWidth,
1639
+ bgGradient,
1640
+ textColor,
1641
+ footerText
1642
+ };
1643
+ }
1644
+ function normalizeBackCover(rawBack, tokenCtx = {}) {
1645
+ if (!rawBack) return void 0;
1646
+ const cfg = typeof rawBack === "object" ? rawBack : {};
1647
+ if (cfg.enabled === false) return void 0;
1648
+ const preset = cfg.preset || "modern";
1649
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
1650
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
1651
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1652
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1653
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1654
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1655
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1656
+ let socialMap;
1657
+ if (cfg.social && typeof cfg.social === "object") {
1658
+ socialMap = {};
1659
+ for (const [k, v] of Object.entries(cfg.social)) {
1660
+ if (typeof v === "string") {
1661
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1662
+ }
1663
+ }
1664
+ }
1665
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1666
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
1667
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1668
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1669
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1670
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1671
+ const logoWidth = cfg.logoWidth;
1672
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1673
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1674
+ return {
1675
+ enabled: true,
1676
+ preset,
1677
+ title,
1678
+ subtitle,
1679
+ company,
1680
+ address,
1681
+ email,
1682
+ phone,
1683
+ website,
1684
+ social: socialMap,
1685
+ copyright,
1686
+ badge,
1687
+ badgeColor,
1688
+ badgeTextColor,
1689
+ logo,
1690
+ logoWidth,
1691
+ bgGradient,
1692
+ textColor
1693
+ };
1694
+ }
1695
+ function normalizeNumberHeadings(raw) {
1696
+ if (raw === void 0 || raw === false) return void 0;
1697
+ if (raw === true) {
1698
+ return { enabled: true, depth: 3, skipH1: false, prefix: "" };
1699
+ }
1700
+ if (typeof raw === "object") {
1701
+ const obj = raw;
1702
+ if (obj.enabled === false) return void 0;
1703
+ return {
1704
+ enabled: true,
1705
+ depth: obj.depth ?? 3,
1706
+ skipH1: obj.skipH1 ?? false,
1707
+ prefix: obj.prefix ?? ""
1708
+ };
1709
+ }
1710
+ return void 0;
1711
+ }
1712
+ function normalizeSecurity(raw) {
1713
+ if (!raw) return void 0;
1714
+ const sec = raw;
1715
+ if (!sec.userPassword && !sec.ownerPassword && !sec.permissions) return void 0;
1716
+ return {
1717
+ userPassword: sec.userPassword,
1718
+ ownerPassword: sec.ownerPassword,
1719
+ permissions: sec.permissions
1720
+ };
1721
+ }
1411
1722
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1412
1723
  const configMeta = userConfig.metadata || {};
1413
1724
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1450,6 +1761,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1450
1761
  const watermark = normalizeWatermark(rawWatermark);
1451
1762
  const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1452
1763
  const signatures = normalizeSignatures(rawSignatures, tokenContext);
1764
+ const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
1765
+ const coverPage = normalizeCoverPage(rawCover, tokenContext);
1766
+ const rawBack = mergedMeta.backCover !== void 0 ? mergedMeta.backCover : userConfig.backCover;
1767
+ const backCover = normalizeBackCover(rawBack, tokenContext);
1768
+ const rawNumberHeadings = mergedMeta.numberHeadings !== void 0 ? mergedMeta.numberHeadings : userConfig.numberHeadings;
1769
+ const numberHeadings = normalizeNumberHeadings(rawNumberHeadings);
1770
+ const rawSecurity = mergedMeta.security || userConfig.security;
1771
+ const security = normalizeSecurity(rawSecurity);
1772
+ const math = mergedMeta.math !== false && userConfig.math !== false;
1453
1773
  const cssList = [];
1454
1774
  const addCss = (item) => {
1455
1775
  if (!item) return;
@@ -1479,6 +1799,11 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1479
1799
  toc,
1480
1800
  signatures,
1481
1801
  watermark,
1802
+ coverPage,
1803
+ backCover,
1804
+ numberHeadings,
1805
+ security,
1806
+ math,
1482
1807
  css: cssList,
1483
1808
  embedImages,
1484
1809
  bundleHtml,
@@ -1486,6 +1811,45 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1486
1811
  };
1487
1812
  }
1488
1813
 
1814
+ // src/core/math/mathRenderer.ts
1815
+ import katex from "katex";
1816
+ function renderMathToHtml(latex, displayMode = false) {
1817
+ try {
1818
+ return katex.renderToString(latex.trim(), {
1819
+ displayMode,
1820
+ throwOnError: false,
1821
+ output: "htmlAndMathml",
1822
+ strict: false
1823
+ });
1824
+ } catch {
1825
+ return `<span class="katex-fallback">${latex}</span>`;
1826
+ }
1827
+ }
1828
+ var KATEX_INLINE_CSS = `
1829
+ .katex { font: normal 1.21em KaTeX_Main, Times New Roman, serif; line-height: 1.2; text-indent: 0; text-rendering: auto; border-color: currentColor; }
1830
+ .katex * { -ms-high-contrast-adjust: none !important; }
1831
+ .katex .katex-html { display: inline-block; }
1832
+ .katex .katex-mathml { clip: rect(1px, 1px, 1px, 1px); border: 0; height: 1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
1833
+ .katex-display { display: block; margin: 1em 0; text-align: center; }
1834
+ .katex-display > .katex { display: inline-block; text-align: initial; }
1835
+ .katex .base { position: relative; white-space: nowrap; width: min-content; }
1836
+ .katex .strut { display: inline-block; }
1837
+ .katex .mord { display: inline-block; }
1838
+ .katex .mbin { display: inline-block; }
1839
+ .katex .mrel { display: inline-block; }
1840
+ .katex .mopen { display: inline-block; }
1841
+ .katex .mclose { display: inline-block; }
1842
+ .katex .mpunct { display: inline-block; }
1843
+ .katex .minner { display: inline-block; }
1844
+ .katex .mop { display: inline-block; }
1845
+ .katex .frac-line { width: 100%; border-bottom-style: solid; }
1846
+ .katex .vlist-t { display: inline-table; table-layout: fixed; }
1847
+ .katex .vlist-r { display: table-row; }
1848
+ .katex .vlist { display: table-cell; vertical-align: bottom; position: relative; }
1849
+ .katex .msupsub { text-align: left; }
1850
+ .katex .sqrt > .root { margin-left: 0.27777778em; margin-right: -0.55555556em; }
1851
+ `;
1852
+
1489
1853
  // src/core/html/htmlBuilder.ts
1490
1854
  function escapeHtml(str) {
1491
1855
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -1528,6 +1892,15 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1528
1892
  result += `<code>${escapeHtml(span.content)}</code>`;
1529
1893
  continue;
1530
1894
  }
1895
+ if (span.type === "mathInline") {
1896
+ result += renderMathToHtml(span.content, false);
1897
+ continue;
1898
+ }
1899
+ if (span.type === "footnoteRef") {
1900
+ const id = escapeHtml(span.footnoteId || span.content);
1901
+ result += `<sup><a href="#fn-${id}" id="fnref-${id}" class="markforge-fnref">[${escapeHtml(span.content)}]</a></sup>`;
1902
+ continue;
1903
+ }
1531
1904
  if (span.type === "htmlInline") {
1532
1905
  result += span.content;
1533
1906
  continue;
@@ -1536,66 +1909,9 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1536
1909
  }
1537
1910
  return result;
1538
1911
  }
1539
- async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1540
- const resolved = resolveDocumentConfig(doc.metadata, config);
1541
- const baseThemeCss = generateThemeCss(resolved.theme);
1542
- let customCss = "";
1543
- for (const cssPath of resolved.css) {
1544
- const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
1545
- if (fs3.existsSync(fullCssPath)) {
1546
- customCss += `
1547
- /* Custom CSS: ${cssPath} */
1548
- ` + fs3.readFileSync(fullCssPath, "utf-8");
1549
- }
1550
- }
1551
- const inlinedCss = doc.inlinedStyles.join("\n");
1912
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1552
1913
  let bodyHtml = "";
1553
- if (resolved.title) {
1554
- bodyHtml += ` <header class="document-header">
1555
- `;
1556
- bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1557
- `;
1558
- if (resolved.subtitle) {
1559
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1560
- `;
1561
- }
1562
- if (resolved.author || resolved.date || resolved.version) {
1563
- bodyHtml += ` <div class="document-meta">
1564
- `;
1565
- if (resolved.author) {
1566
- bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1567
- `;
1568
- }
1569
- if (resolved.version) {
1570
- bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1571
- `;
1572
- }
1573
- if (resolved.date) {
1574
- bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1575
- `;
1576
- }
1577
- bodyHtml += ` </div>
1578
- `;
1579
- }
1580
- bodyHtml += ` </header>
1581
- `;
1582
- }
1583
- if (resolved.toc && doc.tocEntries.length > 0) {
1584
- bodyHtml += ` <nav class="table-of-contents">
1585
- `;
1586
- bodyHtml += ` <h2>Table of Contents</h2>
1587
- <ul>
1588
- `;
1589
- for (const entry of doc.tocEntries) {
1590
- const indent = " ".repeat(entry.level);
1591
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1592
- `;
1593
- }
1594
- bodyHtml += ` </ul>
1595
- </nav>
1596
- `;
1597
- }
1598
- for (const node of doc.nodes) {
1914
+ for (const node of nodes) {
1599
1915
  if (node.type === "heading") {
1600
1916
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1601
1917
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
@@ -1605,6 +1921,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1605
1921
  if (node.type === "paragraph") {
1606
1922
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1607
1923
  bodyHtml += ` <p>${inner}</p>
1924
+ `;
1925
+ continue;
1926
+ }
1927
+ if (node.type === "mathBlock") {
1928
+ bodyHtml += ` <div class="math-block">${renderMathToHtml(node.text || "", true)}</div>
1929
+ `;
1930
+ continue;
1931
+ }
1932
+ if (node.type === "columns") {
1933
+ const cols = node.columnsCount || 2;
1934
+ const gap = node.columnGap || "1.5rem";
1935
+ let colChildrenHtml = "";
1936
+ for (const col of node.children || []) {
1937
+ const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir);
1938
+ colChildrenHtml += ` <div class="markforge-col">
1939
+ ${colInner} </div>
1940
+ `;
1941
+ }
1942
+ bodyHtml += ` <div class="markforge-columns" style="--cols: ${cols}; --col-gap: ${gap};">
1943
+ ${colChildrenHtml} </div>
1608
1944
  `;
1609
1945
  continue;
1610
1946
  }
@@ -1693,95 +2029,522 @@ ${escapeHtml(node.text || "")}
1693
2029
  continue;
1694
2030
  }
1695
2031
  }
1696
- let watermarkCss = "";
1697
- let watermarkHtml = "";
1698
- if (resolved.watermark) {
1699
- const wm = resolved.watermark;
1700
- watermarkCss = `
1701
- .document-watermark {
1702
- position: fixed;
1703
- top: 0;
1704
- left: 0;
1705
- width: 100vw;
1706
- height: 100vh;
1707
- pointer-events: none;
1708
- z-index: 0;
1709
- user-select: none;
1710
- -webkit-user-select: none;
2032
+ return bodyHtml;
2033
+ }
2034
+ async function renderCoverPageHtml(cover, baseDir = process.cwd()) {
2035
+ let logoHtml = "";
2036
+ if (cover.logo) {
2037
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
2038
+ const src = resolvedLogo ? resolvedLogo.dataUri : cover.logo;
2039
+ 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;";
2040
+ logoHtml = `<div class="cover-logo"><img src="${src}" alt="Logo" style="${widthStyle} object-fit: contain;" /></div>`;
1711
2041
  }
1712
- .document-container {
2042
+ const badgeHtml = cover.badge ? `<div class="cover-badge" style="${cover.badgeColor ? `background-color: ${cover.badgeColor};` : ""}${cover.badgeTextColor ? `color: ${cover.badgeTextColor};` : ""}">${escapeHtml(cover.badge)}</div>` : "";
2043
+ const titleHtml = `<h1 class="cover-title">${escapeHtml(cover.title)}</h1>`;
2044
+ const subtitleHtml = cover.subtitle ? `<div class="cover-subtitle">${escapeHtml(cover.subtitle)}</div>` : "";
2045
+ const metaItems = [];
2046
+ 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>`);
2047
+ 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>`);
2048
+ 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>`);
2049
+ 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>`);
2050
+ const metaHtml = metaItems.length > 0 ? `<div class="cover-meta">${metaItems.join("\n")}</div>` : "";
2051
+ const footerHtml = cover.footerText ? `<div class="cover-footer-text">${escapeHtml(cover.footerText)}</div>` : "";
2052
+ const css = `
2053
+ .markforge-cover {
2054
+ min-height: 100vh;
2055
+ box-sizing: border-box;
2056
+ display: flex;
2057
+ flex-direction: column;
2058
+ justify-content: space-between;
2059
+ padding: 4rem 3.5rem;
2060
+ page-break-after: always;
2061
+ break-after: page;
1713
2062
  position: relative;
1714
- z-index: 1;
2063
+ z-index: 2;
2064
+ background: ${cover.bgGradient || "#FFFFFF"};
2065
+ -webkit-print-color-adjust: exact;
2066
+ print-color-adjust: exact;
2067
+ color: ${cover.textColor || "#0F172A"};
2068
+ }
2069
+ .markforge-cover.cover-modern {
2070
+ border-top: 8px solid #0D998D;
2071
+ }
2072
+ .markforge-cover.cover-corporate-split {
2073
+ border-left: 12px solid #0D998D;
2074
+ }
2075
+ .markforge-cover.cover-card {
2076
+ background: #F8FAFC;
2077
+ }
2078
+ .cover-top {
2079
+ display: flex;
2080
+ justify-content: space-between;
2081
+ align-items: flex-start;
2082
+ width: 100%;
2083
+ }
2084
+ .cover-badge {
2085
+ display: inline-block;
2086
+ padding: 0.35rem 0.85rem;
2087
+ font-size: 0.78rem;
2088
+ font-weight: 700;
2089
+ letter-spacing: 0.08em;
2090
+ text-transform: uppercase;
2091
+ background-color: #ECFDFD;
2092
+ color: #0D998D;
2093
+ border-radius: 4px;
2094
+ border: 1px solid #33CDCF;
2095
+ }
2096
+ .cover-body {
2097
+ margin: auto 0;
2098
+ }
2099
+ .cover-title {
2100
+ font-size: 2.8rem;
2101
+ font-weight: 800;
2102
+ line-height: 1.15;
2103
+ margin: 0 0 1rem 0;
2104
+ color: inherit;
2105
+ }
2106
+ .cover-subtitle {
2107
+ font-size: 1.35rem;
2108
+ font-weight: 400;
2109
+ color: #64748B;
2110
+ margin: 0 0 2rem 0;
2111
+ line-height: 1.4;
2112
+ }
2113
+ .cover-meta {
2114
+ display: flex;
2115
+ flex-direction: column;
2116
+ gap: 0.5rem;
2117
+ border-top: 1.5px solid #E2E8F0;
2118
+ padding-top: 1.5rem;
2119
+ max-width: 480px;
2120
+ }
2121
+ .cover-meta-item {
2122
+ font-size: 0.92rem;
2123
+ display: flex;
2124
+ gap: 0.75rem;
2125
+ }
2126
+ .cover-meta-label {
2127
+ font-weight: 600;
2128
+ color: #64748B;
2129
+ min-width: 110px;
2130
+ }
2131
+ .cover-meta-value {
2132
+ font-weight: 500;
2133
+ color: #0F172A;
2134
+ }
2135
+ .cover-bottom {
2136
+ display: flex;
2137
+ justify-content: space-between;
2138
+ align-items: flex-end;
2139
+ font-size: 0.82rem;
2140
+ color: #94A3B8;
1715
2141
  }
1716
2142
  @media print {
1717
- .document-watermark {
1718
- position: fixed;
1719
- top: 0;
1720
- left: 0;
1721
- width: 100vw;
2143
+ .markforge-cover {
2144
+ page-break-after: always;
2145
+ break-after: page;
1722
2146
  height: 100vh;
2147
+ min-height: 100vh;
2148
+ max-height: 100vh;
2149
+ box-sizing: border-box;
2150
+ overflow: hidden;
2151
+ margin: 0;
1723
2152
  -webkit-print-color-adjust: exact;
1724
2153
  print-color-adjust: exact;
1725
2154
  }
1726
2155
  }
1727
2156
  `;
1728
- watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
1729
- <script>
1730
- (function() {
1731
- try {
1732
- var canvas = document.createElement('canvas');
1733
- var dpr = 3;
1734
- var width = 800;
1735
- var height = 1100;
1736
- canvas.width = Math.round(width * dpr);
1737
- canvas.height = Math.round(height * dpr);
1738
- var ctx = canvas.getContext('2d');
1739
- if (ctx) {
1740
- ctx.scale(dpr, dpr);
1741
- ctx.translate(width / 2, height / 2);
1742
- ctx.rotate((${wm.rotate} * Math.PI) / 180);
1743
- ctx.textAlign = 'center';
1744
- ctx.textBaseline = 'middle';
1745
- ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
1746
- ctx.fillStyle = '${wm.color}';
1747
- ctx.globalAlpha = ${wm.opacity};
1748
- try { ctx.letterSpacing = '0.15em'; } catch(e) {}
1749
- ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
1750
- var dataUrl = canvas.toDataURL('image/png');
1751
- var wmEl = document.getElementById('markforge-watermark');
1752
- if (wmEl) {
1753
- wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
1754
- wmEl.style.backgroundRepeat = 'no-repeat';
1755
- wmEl.style.backgroundPosition = 'center center';
1756
- wmEl.style.backgroundSize = 'contain';
1757
- }
1758
- }
1759
- } catch(err) {}
1760
- })();
1761
- </script>
2157
+ const html = ` <section class="markforge-cover cover-${cover.preset}">
2158
+ <div class="cover-top">
2159
+ ${logoHtml}
2160
+ ${badgeHtml}
2161
+ </div>
2162
+ <div class="cover-body">
2163
+ ${titleHtml}
2164
+ ${subtitleHtml}
2165
+ ${metaHtml}
2166
+ </div>
2167
+ <div class="cover-bottom">
2168
+ ${footerHtml}
2169
+ </div>
2170
+ </section>
1762
2171
  `;
2172
+ return { html, css };
2173
+ }
2174
+ async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
2175
+ let logoHtml = "";
2176
+ if (backCover.logo) {
2177
+ const resolved = await resolveImage(backCover.logo, baseDir);
2178
+ const src = resolved ? resolved.dataUri : backCover.logo;
2179
+ const widthStyle = backCover.logoWidth ? `style="width: ${typeof backCover.logoWidth === "number" ? `${backCover.logoWidth}px` : backCover.logoWidth}; max-width: 100%;"` : `style="max-width: 160px; height: auto;"`;
2180
+ logoHtml = `<div class="back-logo"><img src="${src}" alt="Brand Logo" ${widthStyle} /></div>`;
1763
2181
  }
1764
- let signaturesHtml = "";
1765
- let signaturesCss = "";
1766
- if (resolved.signatures && resolved.signatures.items.length > 0) {
1767
- const sig = resolved.signatures;
1768
- const numItems = sig.items.length;
1769
- let justifyCss = "flex-end";
1770
- if (sig.align === "left") justifyCss = "flex-start";
1771
- else if (sig.align === "center") justifyCss = "center";
1772
- else if (sig.align === "space-between") justifyCss = "space-between";
1773
- signaturesCss = `
1774
- .markforge-signatures {
1775
- margin-top: ${sig.spacingBefore};
1776
- display: grid;
1777
- 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))`};
1778
- gap: 2rem;
1779
- page-break-inside: avoid;
1780
- break-inside: avoid;
2182
+ const badgeHtml = backCover.badge ? `<div class="back-badge" style="${backCover.badgeColor ? `background-color: ${backCover.badgeColor};` : ""}${backCover.badgeTextColor ? `color: ${backCover.badgeTextColor};` : ""}">${escapeHtml(backCover.badge)}</div>` : "";
2183
+ const titleHtml = `<h1 class="back-title">${escapeHtml(backCover.title)}</h1>`;
2184
+ const subtitleHtml = backCover.subtitle ? `<div class="back-subtitle">${escapeHtml(backCover.subtitle)}</div>` : "";
2185
+ const contactItems = [];
2186
+ 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>`);
2187
+ 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>`);
2188
+ 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>`);
2189
+ 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>`);
2190
+ 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>`);
2191
+ if (backCover.social) {
2192
+ for (const [network, url] of Object.entries(backCover.social)) {
2193
+ if (url) {
2194
+ 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>`);
2195
+ }
2196
+ }
1781
2197
  }
1782
- .markforge-signature-card {
1783
- ${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
1784
- ${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
2198
+ const contactHtml = contactItems.length > 0 ? `<div class="back-contact-grid">${contactItems.join("\n")}</div>` : "";
2199
+ const copyrightHtml = backCover.copyright ? `<div class="back-copyright">${escapeHtml(backCover.copyright)}</div>` : "";
2200
+ const isDark = backCover.preset === "corporate";
2201
+ const css = `
2202
+ .markforge-back-cover {
2203
+ min-height: 100vh;
2204
+ box-sizing: border-box;
2205
+ display: flex;
2206
+ flex-direction: column;
2207
+ justify-content: space-between;
2208
+ padding: 4rem 3.5rem;
2209
+ page-break-before: always;
2210
+ break-before: page;
2211
+ position: relative;
2212
+ z-index: 2;
2213
+ background: ${backCover.bgGradient || (isDark ? "#0F172A" : "#FFFFFF")};
2214
+ color: ${backCover.textColor || (isDark ? "#F8FAFC" : "#0F172A")};
2215
+ }
2216
+ .markforge-back-cover.back-modern {
2217
+ border-bottom: 8px solid #0D998D;
2218
+ }
2219
+ .markforge-back-cover.back-corporate {
2220
+ border-left: 12px solid #33CDCF;
2221
+ }
2222
+ .markforge-back-cover.back-card {
2223
+ background: #F8FAFC;
2224
+ }
2225
+ .back-top {
2226
+ display: flex;
2227
+ justify-content: space-between;
2228
+ align-items: flex-start;
2229
+ width: 100%;
2230
+ }
2231
+ .back-badge {
2232
+ display: inline-block;
2233
+ padding: 0.35rem 0.85rem;
2234
+ font-size: 0.78rem;
2235
+ font-weight: 700;
2236
+ letter-spacing: 0.08em;
2237
+ text-transform: uppercase;
2238
+ background-color: #ECFDFD;
2239
+ color: #0D998D;
2240
+ border-radius: 4px;
2241
+ border: 1px solid #33CDCF;
2242
+ }
2243
+ .back-body {
2244
+ margin: auto 0;
2245
+ }
2246
+ .back-title {
2247
+ font-size: 2.6rem;
2248
+ font-weight: 800;
2249
+ line-height: 1.15;
2250
+ margin: 0 0 0.75rem 0;
2251
+ color: inherit;
2252
+ }
2253
+ .back-subtitle {
2254
+ font-size: 1.25rem;
2255
+ font-weight: 400;
2256
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2257
+ margin: 0 0 2rem 0;
2258
+ line-height: 1.4;
2259
+ }
2260
+ .back-contact-grid {
2261
+ display: flex;
2262
+ flex-direction: column;
2263
+ gap: 0.6rem;
2264
+ border-top: 1.5px solid ${isDark ? "#334155" : "#E2E8F0"};
2265
+ padding-top: 1.5rem;
2266
+ max-width: 540px;
2267
+ }
2268
+ .back-contact-item {
2269
+ font-size: 0.92rem;
2270
+ display: flex;
2271
+ gap: 0.75rem;
2272
+ }
2273
+ .back-contact-label {
2274
+ font-weight: 600;
2275
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2276
+ min-width: 110px;
2277
+ }
2278
+ .back-contact-link {
2279
+ color: #0D998D;
2280
+ text-decoration: none;
2281
+ font-weight: 600;
2282
+ }
2283
+ .back-contact-link:hover {
2284
+ text-decoration: underline;
2285
+ }
2286
+ .back-copyright {
2287
+ font-size: 0.82rem;
2288
+ color: ${isDark ? "#64748B" : "#94A3B8"};
2289
+ border-top: 1px solid ${isDark ? "#1E293B" : "#F1F5F9"};
2290
+ padding-top: 1rem;
2291
+ margin-top: 2rem;
2292
+ }
2293
+ @media print {
2294
+ .markforge-back-cover {
2295
+ page: back-cover-page;
2296
+ page-break-before: always;
2297
+ break-before: page;
2298
+ page-break-after: avoid;
2299
+ break-after: avoid;
2300
+ min-height: 100vh;
2301
+ height: 100vh;
2302
+ max-height: 100vh;
2303
+ margin: 0;
2304
+ box-sizing: border-box;
2305
+ overflow: hidden;
2306
+ -webkit-print-color-adjust: exact;
2307
+ print-color-adjust: exact;
2308
+ }
2309
+ }
2310
+ `;
2311
+ const html = ` <section class="markforge-back-cover back-${backCover.preset}">
2312
+ <div class="back-top">
2313
+ ${logoHtml}
2314
+ ${badgeHtml}
2315
+ </div>
2316
+ <div class="back-body">
2317
+ ${titleHtml}
2318
+ ${subtitleHtml}
2319
+ ${contactHtml}
2320
+ </div>
2321
+ ${copyrightHtml}
2322
+ </section>
2323
+ `;
2324
+ return { html, css };
2325
+ }
2326
+ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2327
+ var _a;
2328
+ const resolved = resolveDocumentConfig(doc.metadata, config);
2329
+ const baseThemeCss = generateThemeCss(resolved.theme);
2330
+ let customCss = "";
2331
+ for (const cssPath of resolved.css) {
2332
+ const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
2333
+ if (fs3.existsSync(fullCssPath)) {
2334
+ customCss += `
2335
+ /* Custom CSS: ${cssPath} */
2336
+ ` + fs3.readFileSync(fullCssPath, "utf-8");
2337
+ }
2338
+ }
2339
+ const inlinedCss = doc.inlinedStyles.join("\n");
2340
+ const extraCss = `
2341
+ .markforge-columns {
2342
+ display: grid;
2343
+ grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
2344
+ gap: var(--col-gap, 1.5rem);
2345
+ margin: 1.5rem 0;
2346
+ }
2347
+ .markforge-col {
2348
+ min-width: 0;
2349
+ }
2350
+ .markforge-fnref {
2351
+ text-decoration: none;
2352
+ font-size: 0.8em;
2353
+ vertical-align: super;
2354
+ color: #0D998D;
2355
+ font-weight: 700;
2356
+ }
2357
+ .markforge-footnotes {
2358
+ margin-top: 3rem;
2359
+ padding-top: 1rem;
2360
+ font-size: 0.88rem;
2361
+ color: #64748B;
2362
+ }
2363
+ .markforge-footnotes hr {
2364
+ border: 0;
2365
+ border-top: 1px solid #E2E8F0;
2366
+ margin-bottom: 1rem;
2367
+ }
2368
+ .markforge-fn-return {
2369
+ text-decoration: none;
2370
+ color: #0D998D;
2371
+ }
2372
+ .math-block {
2373
+ margin: 1.5rem 0;
2374
+ text-align: center;
2375
+ overflow-x: auto;
2376
+ }
2377
+ `;
2378
+ let coverHtml = "";
2379
+ let coverCss = "";
2380
+ if (resolved.coverPage && resolved.coverPage.enabled) {
2381
+ const coverRes = await renderCoverPageHtml(resolved.coverPage, baseDir);
2382
+ coverHtml = coverRes.html;
2383
+ coverCss = coverRes.css;
2384
+ }
2385
+ let backHtml = "";
2386
+ let backCss = "";
2387
+ if (resolved.backCover && resolved.backCover.enabled) {
2388
+ const backRes = await renderBackCoverHtml(resolved.backCover, baseDir);
2389
+ backHtml = backRes.html;
2390
+ backCss = backRes.css;
2391
+ }
2392
+ let bodyHtml = "";
2393
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2394
+ bodyHtml += ` <header class="document-header">
2395
+ `;
2396
+ bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
2397
+ `;
2398
+ if (resolved.subtitle) {
2399
+ bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
2400
+ `;
2401
+ }
2402
+ if (resolved.author || resolved.date || resolved.version) {
2403
+ bodyHtml += ` <div class="document-meta">
2404
+ `;
2405
+ if (resolved.author) {
2406
+ bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
2407
+ `;
2408
+ }
2409
+ if (resolved.version) {
2410
+ bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
2411
+ `;
2412
+ }
2413
+ if (resolved.date) {
2414
+ bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
2415
+ `;
2416
+ }
2417
+ bodyHtml += ` </div>
2418
+ `;
2419
+ }
2420
+ bodyHtml += ` </header>
2421
+ `;
2422
+ }
2423
+ if (resolved.toc && doc.tocEntries.length > 0) {
2424
+ bodyHtml += ` <nav class="table-of-contents">
2425
+ `;
2426
+ bodyHtml += ` <h2>Table of Contents</h2>
2427
+ <ul>
2428
+ `;
2429
+ for (const entry of doc.tocEntries) {
2430
+ const indent = " ".repeat(entry.level);
2431
+ bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
2432
+ `;
2433
+ }
2434
+ bodyHtml += ` </ul>
2435
+ </nav>
2436
+ `;
2437
+ }
2438
+ bodyHtml += await renderNodesToHtml(doc.nodes, resolved, baseDir);
2439
+ let footnotesHtml = "";
2440
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
2441
+ let fnListHtml = "";
2442
+ for (const def of doc.footnoteDefs) {
2443
+ const defInner = await renderInlinesToHtml(def.inlines, baseDir);
2444
+ fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">&#8617;</a></li>
2445
+ `;
2446
+ }
2447
+ footnotesHtml = `
2448
+ <footer class="markforge-footnotes">
2449
+ <hr />
2450
+ <ol>
2451
+ ${fnListHtml} </ol>
2452
+ </footer>
2453
+ `;
2454
+ }
2455
+ let watermarkCss = "";
2456
+ let watermarkHtml = "";
2457
+ if (resolved.watermark) {
2458
+ const wm = resolved.watermark;
2459
+ watermarkCss = `
2460
+ .document-watermark {
2461
+ position: fixed;
2462
+ top: 0;
2463
+ left: 0;
2464
+ right: 0;
2465
+ bottom: 0;
2466
+ width: 100%;
2467
+ height: 100%;
2468
+ pointer-events: none;
2469
+ z-index: 0;
2470
+ user-select: none;
2471
+ -webkit-user-select: none;
2472
+ -webkit-print-color-adjust: exact;
2473
+ print-color-adjust: exact;
2474
+ }
2475
+ .document-container {
2476
+ position: relative;
2477
+ z-index: 1;
2478
+ }
2479
+ @media print {
2480
+ .document-watermark {
2481
+ position: fixed;
2482
+ top: 0;
2483
+ left: 0;
2484
+ right: 0;
2485
+ bottom: 0;
2486
+ width: 100vw;
2487
+ height: 100vh;
2488
+ pointer-events: none;
2489
+ z-index: 0;
2490
+ -webkit-print-color-adjust: exact;
2491
+ print-color-adjust: exact;
2492
+ }
2493
+ }
2494
+ `;
2495
+ watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
2496
+ <script>
2497
+ (function() {
2498
+ try {
2499
+ var canvas = document.createElement('canvas');
2500
+ var dpr = 2;
2501
+ var width = 1200;
2502
+ var height = 1600;
2503
+ canvas.width = width * dpr;
2504
+ canvas.height = height * dpr;
2505
+ var ctx = canvas.getContext('2d');
2506
+ if (ctx) {
2507
+ ctx.scale(dpr, dpr);
2508
+ ctx.translate(width / 2, height / 2);
2509
+ ctx.rotate((-Math.abs(${wm.rotate || 45}) * Math.PI) / 180);
2510
+ ctx.textAlign = 'center';
2511
+ ctx.textBaseline = 'middle';
2512
+ ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
2513
+ ctx.fillStyle = '${wm.color}';
2514
+ ctx.globalAlpha = ${wm.opacity};
2515
+ try { ctx.letterSpacing = '0.15em'; } catch(e) {}
2516
+ ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
2517
+ var dataUrl = canvas.toDataURL('image/png');
2518
+ var wmEl = document.getElementById('markforge-watermark');
2519
+ if (wmEl) {
2520
+ wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
2521
+ wmEl.style.backgroundRepeat = 'no-repeat';
2522
+ wmEl.style.backgroundPosition = 'center center';
2523
+ wmEl.style.backgroundSize = 'contain';
2524
+ }
2525
+ }
2526
+ } catch(err) {}
2527
+ })();
2528
+ </script>
2529
+ `;
2530
+ }
2531
+ let signaturesHtml = "";
2532
+ let signaturesCss = "";
2533
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
2534
+ const sig = resolved.signatures;
2535
+ const numItems = sig.items.length;
2536
+ signaturesCss = `
2537
+ .markforge-signatures {
2538
+ margin-top: ${sig.spacingBefore};
2539
+ display: grid;
2540
+ 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))`};
2541
+ gap: 2rem;
2542
+ page-break-inside: avoid;
2543
+ break-inside: avoid;
2544
+ }
2545
+ .markforge-signature-card {
2546
+ ${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
2547
+ ${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
1785
2548
  display: flex;
1786
2549
  flex-direction: column;
1787
2550
  ${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
@@ -1881,6 +2644,10 @@ ${itemCards}
1881
2644
  <style>
1882
2645
  ${THEME_COMPONENTS}
1883
2646
  ${baseThemeCss}
2647
+ ${KATEX_INLINE_CSS}
2648
+ ${extraCss}
2649
+ ${coverCss}
2650
+ ${backCss}
1884
2651
  ${customCss}
1885
2652
  ${inlinedCss}
1886
2653
  ${watermarkCss}
@@ -1888,14 +2655,95 @@ ${signaturesCss}
1888
2655
  </style>
1889
2656
  </head>
1890
2657
  <body>
1891
- ${watermarkHtml} <div class="document-container">
1892
- ${bodyHtml}${signaturesHtml} </div>
2658
+ ${watermarkHtml}${coverHtml} <div class="document-container">
2659
+ ${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
1893
2660
  ${mermaidScript}
1894
- </body>
2661
+ ${backHtml}</body>
1895
2662
  </html>`;
1896
2663
  }
1897
2664
 
1898
2665
  // src/core/pdf/pdfBuilder.ts
2666
+ function escapeXml(str) {
2667
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2668
+ }
2669
+ function generateWatermarkPngBuffer(chromePath, wm) {
2670
+ const tmpHtml = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
2671
+ const tmpPng = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
2672
+ try {
2673
+ const text = escapeXml(wm.text.toUpperCase());
2674
+ const fontSize = (wm.fontSize || 52) * 1.5;
2675
+ const color = wm.color || "#E11D48";
2676
+ const opacity = wm.opacity !== void 0 ? wm.opacity : 0.12;
2677
+ const rotate = wm.rotate !== void 0 ? wm.rotate : -45;
2678
+ const html = `<!DOCTYPE html>
2679
+ <html>
2680
+ <head>
2681
+ <meta charset="utf-8">
2682
+ <style>
2683
+ html, body {
2684
+ margin: 0;
2685
+ padding: 0;
2686
+ width: 1200px;
2687
+ height: 1600px;
2688
+ background: transparent;
2689
+ overflow: hidden;
2690
+ }
2691
+ .wm-box {
2692
+ width: 1200px;
2693
+ height: 1600px;
2694
+ display: flex;
2695
+ align-items: center;
2696
+ justify-content: center;
2697
+ transform: rotate(${rotate}deg);
2698
+ }
2699
+ .wm-text {
2700
+ font-family: system-ui, -apple-system, sans-serif;
2701
+ font-weight: 900;
2702
+ font-size: ${fontSize}px;
2703
+ color: ${color};
2704
+ opacity: ${opacity};
2705
+ letter-spacing: 0.15em;
2706
+ text-transform: uppercase;
2707
+ white-space: nowrap;
2708
+ }
2709
+ </style>
2710
+ </head>
2711
+ <body>
2712
+ <div class="wm-box"><span class="wm-text">${text}</span></div>
2713
+ </body>
2714
+ </html>`;
2715
+ fs4.writeFileSync(tmpHtml, html, "utf8");
2716
+ const fileUrl = pathToFileURL2(tmpHtml).href;
2717
+ const isWin = process.platform === "win32";
2718
+ spawnSync(
2719
+ chromePath,
2720
+ [
2721
+ "--headless=new",
2722
+ "--disable-gpu",
2723
+ "--disable-sync",
2724
+ "--disable-extensions",
2725
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2726
+ `--screenshot=${tmpPng}`,
2727
+ "--window-size=1200,1600",
2728
+ "--default-background-color=00000000",
2729
+ fileUrl
2730
+ ],
2731
+ { timeout: 15e3, windowsHide: true }
2732
+ );
2733
+ if (fs4.existsSync(tmpPng) && fs4.statSync(tmpPng).size > 0) {
2734
+ return fs4.readFileSync(tmpPng);
2735
+ }
2736
+ return null;
2737
+ } catch {
2738
+ return null;
2739
+ } finally {
2740
+ try {
2741
+ if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
2742
+ if (fs4.existsSync(tmpPng)) fs4.unlinkSync(tmpPng);
2743
+ } catch {
2744
+ }
2745
+ }
2746
+ }
1899
2747
  function findChromeExecutable() {
1900
2748
  if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1901
2749
  return process.env.CHROME_PATH;
@@ -1960,7 +2808,7 @@ function findChromeExecutable() {
1960
2808
  return null;
1961
2809
  }
1962
2810
  function injectPagedMediaStyles(html, config, metadata) {
1963
- var _a, _b, _c, _d, _e, _f;
2811
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1964
2812
  const resolved = resolveDocumentConfig(metadata || {}, config);
1965
2813
  const size = resolved.paperSize;
1966
2814
  const orientation = resolved.orientation;
@@ -2004,6 +2852,38 @@ function injectPagedMediaStyles(html, config, metadata) {
2004
2852
  ${fontStyle}
2005
2853
  }`;
2006
2854
  };
2855
+ const coverPageCss = ((_a = resolved.coverPage) == null ? void 0 : _a.enabled) ? `
2856
+ @page :first {
2857
+ margin-top: 0;
2858
+ margin-bottom: 0;
2859
+ margin-left: 0;
2860
+ margin-right: 0;
2861
+ background-image: none !important;
2862
+ @top-left { content: none; }
2863
+ @top-center { content: none; }
2864
+ @top-right { content: none; }
2865
+ @bottom-left { content: none; }
2866
+ @bottom-center { content: none; }
2867
+ @bottom-right { content: none; }
2868
+ }` : "";
2869
+ const backCoverCss = ((_b = resolved.backCover) == null ? void 0 : _b.enabled) ? `
2870
+ @page back-cover-page {
2871
+ size: ${size} ${orientation};
2872
+ margin: 0;
2873
+ background-image: none !important;
2874
+ @top-left { content: none; }
2875
+ @top-center { content: none; }
2876
+ @top-right { content: none; }
2877
+ @bottom-left { content: none; }
2878
+ @bottom-center { content: none; }
2879
+ @bottom-right { content: none; }
2880
+ }
2881
+ .markforge-back-cover {
2882
+ page: back-cover-page;
2883
+ min-height: 100vh;
2884
+ height: 100vh;
2885
+ box-sizing: border-box;
2886
+ }` : "";
2007
2887
  const pagedCss = `
2008
2888
  @page {
2009
2889
  size: ${size} ${orientation};
@@ -2011,15 +2891,18 @@ function injectPagedMediaStyles(html, config, metadata) {
2011
2891
  margin-bottom: ${bottom};
2012
2892
  margin-left: ${left};
2013
2893
  margin-right: ${right};
2014
- ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
2015
- ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
2016
- ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
2017
- ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
2018
- ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
2019
- ${buildZoneCss("bottom-right", (_f = resolved.footer) == null ? void 0 : _f.right, true)}
2894
+ ${buildZoneCss("top-left", (_c = resolved.header) == null ? void 0 : _c.left)}
2895
+ ${buildZoneCss("top-center", (_d = resolved.header) == null ? void 0 : _d.center)}
2896
+ ${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
2897
+ ${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
2898
+ ${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
2899
+ ${buildZoneCss("bottom-right", (_h = resolved.footer) == null ? void 0 : _h.right, true)}
2020
2900
  }
2901
+ ${coverPageCss}
2902
+ ${backCoverCss}
2021
2903
  @media print {
2022
2904
  body { padding: 0; }
2905
+ .document-watermark { display: none !important; }
2023
2906
  h1, h2, h3, pre, table, blockquote, .callout {
2024
2907
  break-inside: avoid;
2025
2908
  }
@@ -2067,6 +2950,7 @@ startxref
2067
2950
  return Buffer.from(pdfBody, "utf-8");
2068
2951
  }
2069
2952
  async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2953
+ var _a, _b, _c;
2070
2954
  const baseHtml = await buildHtmlDocument(doc, config, baseDir);
2071
2955
  const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
2072
2956
  const chromePath = findChromeExecutable();
@@ -2076,6 +2960,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2076
2960
  const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
2077
2961
  const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
2078
2962
  const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
2963
+ const isWin = process.platform === "win32";
2079
2964
  const isolatedFlags = [
2080
2965
  `--user-data-dir=${tmpProfile}`,
2081
2966
  "--no-first-run",
@@ -2086,7 +2971,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2086
2971
  "--disable-default-apps",
2087
2972
  "--disable-extensions",
2088
2973
  "--disable-domain-reliability",
2089
- "--disable-client-side-phishing-detection",
2090
2974
  "--disable-breakpad",
2091
2975
  "--disable-component-extensions-with-background-pages",
2092
2976
  "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
@@ -2095,12 +2979,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2095
2979
  "--mute-audio",
2096
2980
  "--no-service-autorun",
2097
2981
  "--disable-gpu",
2098
- "--no-sandbox",
2099
- "--disable-setuid-sandbox",
2100
- "--allow-file-access-from-files",
2101
- "--disable-web-security",
2982
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2102
2983
  "--force-color-profile=srgb",
2103
- "--no-pdf-header-footer"
2984
+ "--no-pdf-header-footer",
2985
+ "--window-size=1200,1600"
2104
2986
  ];
2105
2987
  try {
2106
2988
  fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
@@ -2115,7 +2997,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2115
2997
  `--print-to-pdf=${tmpPdf}`,
2116
2998
  fileUrl
2117
2999
  ],
2118
- { timeout: 3e4 }
3000
+ { timeout: 3e4, windowsHide: true }
2119
3001
  );
2120
3002
  if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
2121
3003
  res = spawnSync(
@@ -2126,12 +3008,78 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2126
3008
  `--print-to-pdf=${tmpPdf}`,
2127
3009
  fileUrl
2128
3010
  ],
2129
- { timeout: 3e4 }
3011
+ { timeout: 3e4, windowsHide: true }
2130
3012
  );
2131
3013
  }
2132
3014
  if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
2133
3015
  const pdfBuffer = fs4.readFileSync(tmpPdf);
2134
- return pdfBuffer;
3016
+ try {
3017
+ const pdfDoc = await PDFDocument.load(pdfBuffer);
3018
+ const resolved = resolveDocumentConfig(doc.metadata, config);
3019
+ if (resolved.title) pdfDoc.setTitle(resolved.title);
3020
+ if (resolved.author) pdfDoc.setAuthor(resolved.author);
3021
+ if (resolved.subtitle) pdfDoc.setSubject(resolved.subtitle);
3022
+ pdfDoc.setCreator("MarkForge Enterprise Document Generator");
3023
+ pdfDoc.setProducer("MarkForge (by Ma'sum)");
3024
+ pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
3025
+ if (((_a = resolved.backCover) == null ? void 0 : _a.enabled) && pdfDoc.getPageCount() > 2) {
3026
+ pdfDoc.removePage(pdfDoc.getPageCount() - 1);
3027
+ }
3028
+ if (resolved.watermark) {
3029
+ const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
3030
+ if (wmPng) {
3031
+ const embeddedPng = await pdfDoc.embedPng(wmPng);
3032
+ const pages = pdfDoc.getPages();
3033
+ const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
3034
+ const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? pages.length - 1 : pages.length;
3035
+ for (let i = startPageIndex; i < endPageIndex; i++) {
3036
+ const page = pages[i];
3037
+ const { width, height } = page.getSize();
3038
+ page.drawImage(embeddedPng, {
3039
+ x: 0,
3040
+ y: 0,
3041
+ width,
3042
+ height
3043
+ });
3044
+ }
3045
+ }
3046
+ }
3047
+ const savedBytes = await pdfDoc.save();
3048
+ let finalBuffer = Buffer.from(savedBytes);
3049
+ if (resolved.security) {
3050
+ const sec = resolved.security;
3051
+ const hasUserPassword = typeof sec.userPassword === "string" && sec.userPassword.length > 0;
3052
+ const hasOwnerPassword = typeof sec.ownerPassword === "string" && sec.ownerPassword.length > 0;
3053
+ if (hasUserPassword || hasOwnerPassword) {
3054
+ try {
3055
+ const userPass = sec.userPassword ?? "";
3056
+ const ownerPass = sec.ownerPassword ?? userPass;
3057
+ const perms = sec.permissions;
3058
+ const encryptedBytes = await encryptPDF(
3059
+ new Uint8Array(finalBuffer),
3060
+ userPass,
3061
+ {
3062
+ ownerPassword: ownerPass,
3063
+ algorithm: "AES-256",
3064
+ allowPrinting: (perms == null ? void 0 : perms.printing) !== "none",
3065
+ allowHighQualityPrint: (perms == null ? void 0 : perms.printing) === "highResolution",
3066
+ allowModifying: (perms == null ? void 0 : perms.modifying) ?? true,
3067
+ allowCopying: (perms == null ? void 0 : perms.copying) ?? true,
3068
+ allowAnnotating: (perms == null ? void 0 : perms.annotating) ?? true,
3069
+ allowFillingForms: (perms == null ? void 0 : perms.fillingForms) ?? true,
3070
+ allowExtraction: (perms == null ? void 0 : perms.contentAccessibility) ?? true,
3071
+ allowAssembly: (perms == null ? void 0 : perms.documentAssembly) ?? true
3072
+ }
3073
+ );
3074
+ finalBuffer = Buffer.from(encryptedBytes);
3075
+ } catch {
3076
+ }
3077
+ }
3078
+ }
3079
+ return finalBuffer;
3080
+ } catch {
3081
+ return pdfBuffer;
3082
+ }
2135
3083
  }
2136
3084
  } catch {
2137
3085
  } finally {
@@ -2386,6 +3334,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2386
3334
  );
2387
3335
  continue;
2388
3336
  }
3337
+ if (span.type === "mathInline") {
3338
+ runs.push(
3339
+ new TextRun({
3340
+ text: span.content,
3341
+ font: "Cambria Math",
3342
+ italics: true,
3343
+ size: options.size,
3344
+ color: options.color || "0F172A"
3345
+ })
3346
+ );
3347
+ continue;
3348
+ }
3349
+ if (span.type === "footnoteRef") {
3350
+ runs.push(
3351
+ new TextRun({
3352
+ text: `[${span.content}]`,
3353
+ superScript: true,
3354
+ color: "009DA0",
3355
+ bold: true
3356
+ })
3357
+ );
3358
+ continue;
3359
+ }
2389
3360
  runs.push(
2390
3361
  new TextRun({
2391
3362
  text: span.content,
@@ -2400,7 +3371,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2400
3371
  return runs;
2401
3372
  }
2402
3373
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2403
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3374
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2404
3375
  const resolved = resolveDocumentConfig(doc.metadata, config);
2405
3376
  const docElements = [];
2406
3377
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -2411,7 +3382,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2411
3382
  const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2412
3383
  const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2413
3384
  const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2414
- if (resolved.title) {
3385
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2415
3386
  docElements.push(
2416
3387
  new Paragraph({
2417
3388
  children: [
@@ -2827,7 +3798,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2827
3798
  }
2828
3799
  if (node.type === "table" && node.children) {
2829
3800
  const tableRows = [];
2830
- const numCols = ((_b = (_a = node.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) || 1;
3801
+ const numCols = ((_c = (_b = node.children[0]) == null ? void 0 : _b.children) == null ? void 0 : _c.length) || 1;
2831
3802
  const colWidth = Math.floor(9e3 / numCols);
2832
3803
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2833
3804
  const rowNode = node.children[rowIdx];
@@ -2837,7 +3808,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2837
3808
  if (rowNode.children) {
2838
3809
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2839
3810
  const cellNode = rowNode.children[colIdx];
2840
- const align = (_c = node.align) == null ? void 0 : _c[colIdx];
3811
+ const align = (_d = node.align) == null ? void 0 : _d[colIdx];
2841
3812
  let alignment = AlignmentType.LEFT;
2842
3813
  if (align === "center") alignment = AlignmentType.CENTER;
2843
3814
  if (align === "right") alignment = AlignmentType.RIGHT;
@@ -2976,12 +3947,119 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2976
3947
  }
2977
3948
  continue;
2978
3949
  }
2979
- }
2980
- if (resolved.signatures && resolved.signatures.items.length > 0) {
2981
- const sig = resolved.signatures;
2982
- const numItems = sig.items.length;
2983
- const contentWidth = Math.max(
2984
- 1e3,
3950
+ if (node.type === "mathBlock") {
3951
+ docElements.push(
3952
+ new Paragraph({
3953
+ alignment: AlignmentType.CENTER,
3954
+ children: [
3955
+ new TextRun({
3956
+ text: node.text || "",
3957
+ font: "Cambria Math",
3958
+ italics: true,
3959
+ size: 24,
3960
+ // 12pt
3961
+ color: textHex
3962
+ })
3963
+ ],
3964
+ spacing: { before: 180, after: 180 },
3965
+ shading: { fill: cardBgHex, type: ShadingType.CLEAR },
3966
+ border: {
3967
+ top: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
3968
+ bottom: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
3969
+ left: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
3970
+ right: { style: BorderStyle.SINGLE, size: 4, color: borderHex }
3971
+ }
3972
+ })
3973
+ );
3974
+ continue;
3975
+ }
3976
+ if (node.type === "columns") {
3977
+ const cols = node.columnsCount || 2;
3978
+ const contentWidth = Math.max(
3979
+ 1e3,
3980
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
3981
+ );
3982
+ const cellWidthDxa = Math.floor(contentWidth / cols);
3983
+ const cells = [];
3984
+ for (const col of node.children || []) {
3985
+ const colParagraphs = [];
3986
+ for (const childNode of col.children || []) {
3987
+ if (childNode.type === "heading") {
3988
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, bold: true, size: 24, color: primaryDarkHex });
3989
+ colParagraphs.push(new Paragraph({ children: runs, spacing: { before: 120, after: 60 } }));
3990
+ } else if (childNode.type === "paragraph") {
3991
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
3992
+ colParagraphs.push(new Paragraph({ children: runs, spacing: { after: 100 } }));
3993
+ } else if (childNode.type === "list" && childNode.children) {
3994
+ for (const item of childNode.children) {
3995
+ const runs = await convertInlinesToTextRuns(item.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
3996
+ colParagraphs.push(new Paragraph({ children: [new TextRun({ text: "\u2022 ", font: defaultFont, color: primaryHex }), ...runs], spacing: { after: 40 } }));
3997
+ }
3998
+ }
3999
+ }
4000
+ if (colParagraphs.length === 0) colParagraphs.push(new Paragraph({}));
4001
+ cells.push(
4002
+ new TableCell({
4003
+ width: { size: cellWidthDxa, type: WidthType.DXA },
4004
+ borders: {
4005
+ top: { style: BorderStyle.NONE, size: 0, color: "auto" },
4006
+ bottom: { style: BorderStyle.NONE, size: 0, color: "auto" },
4007
+ left: { style: BorderStyle.NONE, size: 0, color: "auto" },
4008
+ right: { style: BorderStyle.NONE, size: 0, color: "auto" }
4009
+ },
4010
+ margins: { top: 60, bottom: 60, left: 100, right: 100 },
4011
+ children: colParagraphs
4012
+ })
4013
+ );
4014
+ }
4015
+ docElements.push(
4016
+ new Table({
4017
+ width: { size: 100, type: WidthType.PERCENTAGE },
4018
+ rows: [new TableRow({ children: cells })]
4019
+ })
4020
+ );
4021
+ docElements.push(new Paragraph({ spacing: { after: 120 } }));
4022
+ continue;
4023
+ }
4024
+ }
4025
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
4026
+ docElements.push(
4027
+ new Paragraph({
4028
+ border: {
4029
+ top: { style: BorderStyle.SINGLE, size: 4, color: borderHex, space: 8 }
4030
+ },
4031
+ spacing: { before: 360, after: 120 }
4032
+ })
4033
+ );
4034
+ for (const def of doc.footnoteDefs) {
4035
+ const defRuns = await convertInlinesToTextRuns(def.inlines, baseDir, {
4036
+ font: defaultFont,
4037
+ size: 18,
4038
+ // 9pt
4039
+ color: textMutedHex
4040
+ });
4041
+ docElements.push(
4042
+ new Paragraph({
4043
+ children: [
4044
+ new TextRun({
4045
+ text: `[${def.id}] `,
4046
+ bold: true,
4047
+ color: primaryDarkHex,
4048
+ font: defaultFont,
4049
+ size: 18
4050
+ }),
4051
+ ...defRuns
4052
+ ],
4053
+ spacing: { after: 60 }
4054
+ })
4055
+ );
4056
+ }
4057
+ }
4058
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
4059
+ const sig = resolved.signatures;
4060
+ const numItems = sig.items.length;
4061
+ const contentWidth = Math.max(
4062
+ 1e3,
2985
4063
  resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2986
4064
  );
2987
4065
  docElements.push(new Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
@@ -3037,7 +4115,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3037
4115
  const centerPos = Math.round(contentWidthTwip / 2);
3038
4116
  const rightPos = contentWidthTwip;
3039
4117
  const headerRuns = [];
3040
- if ((_d = resolved.header) == null ? void 0 : _d.left) {
4118
+ if ((_e = resolved.header) == null ? void 0 : _e.left) {
3041
4119
  headerRuns.push(
3042
4120
  new TextRun({
3043
4121
  text: resolved.header.left.text,
@@ -3050,7 +4128,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3050
4128
  );
3051
4129
  }
3052
4130
  headerRuns.push(new TextRun({ text: " " }));
3053
- if ((_e = resolved.header) == null ? void 0 : _e.center) {
4131
+ if ((_f = resolved.header) == null ? void 0 : _f.center) {
3054
4132
  headerRuns.push(
3055
4133
  new TextRun({
3056
4134
  text: resolved.header.center.text,
@@ -3063,7 +4141,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3063
4141
  );
3064
4142
  }
3065
4143
  headerRuns.push(new TextRun({ text: " " }));
3066
- if ((_f = resolved.header) == null ? void 0 : _f.right) {
4144
+ if ((_g = resolved.header) == null ? void 0 : _g.right) {
3067
4145
  headerRuns.push(
3068
4146
  new TextRun({
3069
4147
  text: resolved.header.right.text,
@@ -3102,7 +4180,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3102
4180
  ]
3103
4181
  }) : void 0;
3104
4182
  const footerRuns = [];
3105
- if ((_g = resolved.footer) == null ? void 0 : _g.left) {
4183
+ if ((_h = resolved.footer) == null ? void 0 : _h.left) {
3106
4184
  footerRuns.push(
3107
4185
  new TextRun({
3108
4186
  text: resolved.footer.left.text,
@@ -3115,7 +4193,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3115
4193
  );
3116
4194
  }
3117
4195
  footerRuns.push(new TextRun({ text: " " }));
3118
- if ((_h = resolved.footer) == null ? void 0 : _h.center) {
4196
+ if ((_i = resolved.footer) == null ? void 0 : _i.center) {
3119
4197
  footerRuns.push(
3120
4198
  new TextRun({
3121
4199
  text: resolved.footer.center.text,
@@ -3128,7 +4206,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3128
4206
  );
3129
4207
  }
3130
4208
  footerRuns.push(new TextRun({ text: " " }));
3131
- if ((_i = resolved.footer) == null ? void 0 : _i.right) {
4209
+ if ((_j = resolved.footer) == null ? void 0 : _j.right) {
3132
4210
  const rZone = resolved.footer.right;
3133
4211
  const rColor = rZone.color.replace("#", "");
3134
4212
  const rSize = (rZone.fontSize || 9) * 2;
@@ -3213,6 +4291,91 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3213
4291
  ]
3214
4292
  }) : void 0;
3215
4293
  const isLandscape = resolved.orientation === "landscape";
4294
+ const docSections = [];
4295
+ if (resolved.coverPage && resolved.coverPage.enabled) {
4296
+ const coverElements = await buildDocxCoverPageElements(
4297
+ resolved.coverPage,
4298
+ defaultFont,
4299
+ textHex,
4300
+ primaryHex,
4301
+ primaryDarkHex,
4302
+ textMutedHex,
4303
+ baseDir
4304
+ );
4305
+ docSections.push({
4306
+ properties: {
4307
+ page: {
4308
+ size: {
4309
+ width: resolved.paperDimensions.widthTwip,
4310
+ height: resolved.paperDimensions.heightTwip,
4311
+ orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
4312
+ },
4313
+ margin: {
4314
+ top: resolved.margins.topTwip,
4315
+ bottom: resolved.margins.bottomTwip,
4316
+ left: resolved.margins.leftTwip,
4317
+ right: resolved.margins.rightTwip
4318
+ }
4319
+ }
4320
+ },
4321
+ headers: void 0,
4322
+ footers: void 0,
4323
+ children: coverElements
4324
+ });
4325
+ }
4326
+ docSections.push({
4327
+ properties: {
4328
+ page: {
4329
+ size: {
4330
+ width: resolved.paperDimensions.widthTwip,
4331
+ height: resolved.paperDimensions.heightTwip,
4332
+ orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
4333
+ },
4334
+ margin: {
4335
+ top: resolved.margins.topTwip,
4336
+ bottom: resolved.margins.bottomTwip,
4337
+ left: resolved.margins.leftTwip,
4338
+ right: resolved.margins.rightTwip,
4339
+ header: 720,
4340
+ footer: 720
4341
+ }
4342
+ }
4343
+ },
4344
+ headers: docHeader ? { default: docHeader } : void 0,
4345
+ footers: docFooter ? { default: docFooter } : void 0,
4346
+ children: docElements
4347
+ });
4348
+ if (resolved.backCover && resolved.backCover.enabled) {
4349
+ const backElements = await buildDocxBackCoverElements(
4350
+ resolved.backCover,
4351
+ defaultFont,
4352
+ textHex,
4353
+ primaryHex,
4354
+ primaryDarkHex,
4355
+ textMutedHex,
4356
+ baseDir
4357
+ );
4358
+ docSections.push({
4359
+ properties: {
4360
+ page: {
4361
+ size: {
4362
+ width: resolved.paperDimensions.widthTwip,
4363
+ height: resolved.paperDimensions.heightTwip,
4364
+ orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
4365
+ },
4366
+ margin: {
4367
+ top: resolved.margins.topTwip,
4368
+ bottom: resolved.margins.bottomTwip,
4369
+ left: resolved.margins.leftTwip,
4370
+ right: resolved.margins.rightTwip
4371
+ }
4372
+ }
4373
+ },
4374
+ headers: void 0,
4375
+ footers: void 0,
4376
+ children: backElements
4377
+ });
4378
+ }
3216
4379
  const document = new Document({
3217
4380
  styles: {
3218
4381
  default: {
@@ -3233,33 +4396,250 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3233
4396
  }
3234
4397
  }
3235
4398
  },
3236
- sections: [
3237
- {
3238
- properties: {
3239
- page: {
3240
- size: {
3241
- width: resolved.paperDimensions.widthTwip,
3242
- height: resolved.paperDimensions.heightTwip,
3243
- orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
3244
- },
3245
- margin: {
3246
- top: resolved.margins.topTwip,
3247
- bottom: resolved.margins.bottomTwip,
3248
- left: resolved.margins.leftTwip,
3249
- right: resolved.margins.rightTwip,
3250
- header: 720,
3251
- footer: 720
3252
- }
3253
- }
3254
- },
3255
- headers: docHeader ? { default: docHeader } : void 0,
3256
- footers: docFooter ? { default: docFooter } : void 0,
3257
- children: docElements
3258
- }
3259
- ]
4399
+ sections: docSections
3260
4400
  });
3261
4401
  return await Packer.toBuffer(document);
3262
4402
  }
4403
+ async function buildDocxBackCoverElements(backCover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4404
+ var _a;
4405
+ const elements = [];
4406
+ elements.push(new Paragraph({ spacing: { before: 1800 } }));
4407
+ if (backCover.logo) {
4408
+ const resolvedLogo = await resolveImage(backCover.logo, baseDir);
4409
+ if (resolvedLogo) {
4410
+ const logoW = typeof backCover.logoWidth === "number" ? backCover.logoWidth : 140;
4411
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4412
+ elements.push(
4413
+ new Paragraph({
4414
+ children: [
4415
+ new ImageRun({
4416
+ data: resolvedLogo.buffer,
4417
+ transformation: {
4418
+ width: logoW,
4419
+ height: Math.round(logoW * 0.75)
4420
+ },
4421
+ type: logoType
4422
+ })
4423
+ ],
4424
+ spacing: { after: 240 }
4425
+ })
4426
+ );
4427
+ }
4428
+ }
4429
+ if (backCover.badge) {
4430
+ elements.push(
4431
+ new Paragraph({
4432
+ children: [
4433
+ new TextRun({
4434
+ text: `[ ${backCover.badge.toUpperCase()} ]`,
4435
+ font: defaultFont,
4436
+ size: 20,
4437
+ bold: true,
4438
+ color: primaryDarkHex
4439
+ })
4440
+ ],
4441
+ spacing: { after: 240 }
4442
+ })
4443
+ );
4444
+ }
4445
+ elements.push(
4446
+ new Paragraph({
4447
+ children: [
4448
+ new TextRun({
4449
+ text: backCover.title,
4450
+ font: defaultFont,
4451
+ size: 52,
4452
+ // 26pt
4453
+ bold: true,
4454
+ color: textHex
4455
+ })
4456
+ ],
4457
+ spacing: { after: 140 }
4458
+ })
4459
+ );
4460
+ if (backCover.subtitle) {
4461
+ elements.push(
4462
+ new Paragraph({
4463
+ children: [
4464
+ new TextRun({
4465
+ text: backCover.subtitle,
4466
+ font: defaultFont,
4467
+ size: 24,
4468
+ // 12pt
4469
+ color: textMutedHex
4470
+ })
4471
+ ],
4472
+ spacing: { after: 480 }
4473
+ })
4474
+ );
4475
+ }
4476
+ elements.push(
4477
+ new Paragraph({
4478
+ border: {
4479
+ bottom: { style: BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4480
+ },
4481
+ spacing: { after: 480 }
4482
+ })
4483
+ );
4484
+ const contactRuns = [];
4485
+ if (backCover.company) contactRuns.push(new TextRun({ text: `Organization: ${backCover.company}
4486
+ `, font: defaultFont, size: 21, color: textHex }));
4487
+ if (backCover.address) contactRuns.push(new TextRun({ text: `Address: ${backCover.address}
4488
+ `, font: defaultFont, size: 21, color: textHex }));
4489
+ if (backCover.email) contactRuns.push(new TextRun({ text: `Email: ${backCover.email}
4490
+ `, font: defaultFont, size: 21, color: textHex }));
4491
+ if (backCover.phone) contactRuns.push(new TextRun({ text: `Phone: ${backCover.phone}
4492
+ `, font: defaultFont, size: 21, color: textHex }));
4493
+ if (backCover.website) contactRuns.push(new TextRun({ text: `Website: ${backCover.website}
4494
+ `, font: defaultFont, size: 21, color: textHex }));
4495
+ if (backCover.social) {
4496
+ for (const [net, url] of Object.entries(backCover.social)) {
4497
+ if (url) {
4498
+ contactRuns.push(new TextRun({ text: `${net.toUpperCase()}: ${url}
4499
+ `, font: defaultFont, size: 21, color: textHex }));
4500
+ }
4501
+ }
4502
+ }
4503
+ if (contactRuns.length > 0) {
4504
+ elements.push(
4505
+ new Paragraph({
4506
+ children: contactRuns,
4507
+ spacing: { before: 360, after: 360 }
4508
+ })
4509
+ );
4510
+ }
4511
+ if (backCover.copyright) {
4512
+ elements.push(
4513
+ new Paragraph({
4514
+ children: [
4515
+ new TextRun({
4516
+ text: backCover.copyright,
4517
+ font: defaultFont,
4518
+ size: 18,
4519
+ color: "94A3B8"
4520
+ })
4521
+ ],
4522
+ spacing: { before: 720 }
4523
+ })
4524
+ );
4525
+ }
4526
+ return elements;
4527
+ }
4528
+ async function buildDocxCoverPageElements(cover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4529
+ var _a;
4530
+ const elements = [];
4531
+ elements.push(new Paragraph({ spacing: { before: 1800 } }));
4532
+ if (cover.logo) {
4533
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
4534
+ if (resolvedLogo) {
4535
+ const logoW = typeof cover.logoWidth === "number" ? cover.logoWidth : 140;
4536
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4537
+ elements.push(
4538
+ new Paragraph({
4539
+ children: [
4540
+ new ImageRun({
4541
+ data: resolvedLogo.buffer,
4542
+ transformation: {
4543
+ width: logoW,
4544
+ height: Math.round(logoW * 0.75)
4545
+ },
4546
+ type: logoType
4547
+ })
4548
+ ],
4549
+ spacing: { after: 240 }
4550
+ })
4551
+ );
4552
+ }
4553
+ }
4554
+ if (cover.badge) {
4555
+ elements.push(
4556
+ new Paragraph({
4557
+ children: [
4558
+ new TextRun({
4559
+ text: `[ ${cover.badge.toUpperCase()} ]`,
4560
+ font: defaultFont,
4561
+ size: 20,
4562
+ bold: true,
4563
+ color: primaryDarkHex
4564
+ })
4565
+ ],
4566
+ spacing: { after: 240 }
4567
+ })
4568
+ );
4569
+ }
4570
+ elements.push(
4571
+ new Paragraph({
4572
+ children: [
4573
+ new TextRun({
4574
+ text: cover.title,
4575
+ font: defaultFont,
4576
+ size: 56,
4577
+ // 28pt
4578
+ bold: true,
4579
+ color: textHex
4580
+ })
4581
+ ],
4582
+ spacing: { after: 140 }
4583
+ })
4584
+ );
4585
+ if (cover.subtitle) {
4586
+ elements.push(
4587
+ new Paragraph({
4588
+ children: [
4589
+ new TextRun({
4590
+ text: cover.subtitle,
4591
+ font: defaultFont,
4592
+ size: 26,
4593
+ // 13pt
4594
+ color: textMutedHex
4595
+ })
4596
+ ],
4597
+ spacing: { after: 480 }
4598
+ })
4599
+ );
4600
+ }
4601
+ elements.push(
4602
+ new Paragraph({
4603
+ border: {
4604
+ bottom: { style: BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4605
+ },
4606
+ spacing: { after: 480 }
4607
+ })
4608
+ );
4609
+ if (cover.company || cover.author || cover.version || cover.date) {
4610
+ const metaRuns = [];
4611
+ if (cover.company) metaRuns.push(new TextRun({ text: `Organization: ${cover.company}
4612
+ `, font: defaultFont, size: 21, color: textHex }));
4613
+ if (cover.author) metaRuns.push(new TextRun({ text: `Author: ${cover.author}
4614
+ `, font: defaultFont, size: 21, color: textHex }));
4615
+ if (cover.version) metaRuns.push(new TextRun({ text: `Version: ${cover.version}
4616
+ `, font: defaultFont, size: 21, color: textHex }));
4617
+ if (cover.date) metaRuns.push(new TextRun({ text: `Date: ${cover.date}
4618
+ `, font: defaultFont, size: 21, color: textHex }));
4619
+ elements.push(
4620
+ new Paragraph({
4621
+ children: metaRuns,
4622
+ spacing: { before: 360, after: 360 }
4623
+ })
4624
+ );
4625
+ }
4626
+ if (cover.footerText) {
4627
+ elements.push(
4628
+ new Paragraph({
4629
+ children: [
4630
+ new TextRun({
4631
+ text: cover.footerText,
4632
+ font: defaultFont,
4633
+ size: 18,
4634
+ color: "94A3B8"
4635
+ })
4636
+ ],
4637
+ spacing: { before: 720 }
4638
+ })
4639
+ );
4640
+ }
4641
+ return elements;
4642
+ }
3263
4643
  async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
3264
4644
  const cellParagraphs = [];
3265
4645
  if (item.title) {
@@ -3487,14 +4867,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
3487
4867
  };
3488
4868
  }
3489
4869
 
4870
+ // src/server/previewServer.ts
4871
+ import * as http from "http";
4872
+ import * as fs7 from "fs";
4873
+ import * as path7 from "path";
4874
+ async function startPreviewServer(options) {
4875
+ const absoluteFilePath = path7.resolve(process.cwd(), options.filePath);
4876
+ if (!fs7.existsSync(absoluteFilePath)) {
4877
+ throw new Error(`MarkForge preview error: File not found at "${absoluteFilePath}"`);
4878
+ }
4879
+ const baseDir = path7.dirname(absoluteFilePath);
4880
+ const { config: fileConfig } = await loadConfig(void 0, baseDir);
4881
+ const baseConfig = options.config || fileConfig;
4882
+ const port = options.port || 3e3;
4883
+ const sseClients = /* @__PURE__ */ new Set();
4884
+ const broadcastReload = () => {
4885
+ sseClients.forEach((client) => {
4886
+ try {
4887
+ client.write(`event: reload
4888
+ data: ${Date.now()}
4889
+
4890
+ `);
4891
+ } catch {
4892
+ sseClients.delete(client);
4893
+ }
4894
+ });
4895
+ };
4896
+ let debounceTimer = null;
4897
+ const watcher = fs7.watch(baseDir, { recursive: false }, (_event, filename) => {
4898
+ if (!filename) return;
4899
+ const changedPath = path7.resolve(baseDir, filename);
4900
+ if (changedPath === absoluteFilePath || filename.includes("markforge") || filename.endsWith(".css")) {
4901
+ if (debounceTimer) clearTimeout(debounceTimer);
4902
+ debounceTimer = setTimeout(() => {
4903
+ broadcastReload();
4904
+ }, 150);
4905
+ }
4906
+ });
4907
+ const server = http.createServer(async (req, res) => {
4908
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
4909
+ if (url.pathname === "/events") {
4910
+ res.writeHead(200, {
4911
+ "Content-Type": "text/event-stream",
4912
+ "Cache-Control": "no-cache, no-transform",
4913
+ Connection: "keep-alive"
4914
+ });
4915
+ res.write(`data: connected
4916
+
4917
+ `);
4918
+ sseClients.add(res);
4919
+ req.on("close", () => {
4920
+ sseClients.delete(res);
4921
+ });
4922
+ return;
4923
+ }
4924
+ if (url.pathname === "/api/file-content" && req.method === "GET") {
4925
+ try {
4926
+ const content = fs7.readFileSync(absoluteFilePath, "utf-8");
4927
+ res.writeHead(200, { "Content-Type": "application/json" });
4928
+ res.end(
4929
+ JSON.stringify({
4930
+ content,
4931
+ fileName: path7.basename(absoluteFilePath),
4932
+ filePath: absoluteFilePath
4933
+ })
4934
+ );
4935
+ } catch (err) {
4936
+ const msg = err instanceof Error ? err.message : String(err);
4937
+ res.writeHead(500, { "Content-Type": "application/json" });
4938
+ res.end(JSON.stringify({ error: msg }));
4939
+ }
4940
+ return;
4941
+ }
4942
+ if (url.pathname === "/api/save-content" && req.method === "POST") {
4943
+ let body = "";
4944
+ req.on("data", (chunk) => {
4945
+ body += chunk;
4946
+ });
4947
+ req.on("end", () => {
4948
+ try {
4949
+ const parsed = JSON.parse(body);
4950
+ if (typeof parsed.content === "string") {
4951
+ fs7.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
4952
+ broadcastReload();
4953
+ res.writeHead(200, { "Content-Type": "application/json" });
4954
+ res.end(JSON.stringify({ success: true, savedAt: Date.now() }));
4955
+ } else {
4956
+ res.writeHead(400, { "Content-Type": "application/json" });
4957
+ res.end(JSON.stringify({ error: "Missing content field in request body" }));
4958
+ }
4959
+ } catch (err) {
4960
+ const msg = err instanceof Error ? err.message : String(err);
4961
+ res.writeHead(500, { "Content-Type": "application/json" });
4962
+ res.end(JSON.stringify({ error: msg }));
4963
+ }
4964
+ });
4965
+ return;
4966
+ }
4967
+ if (url.pathname === "/api/export" && (req.method === "GET" || req.method === "POST")) {
4968
+ const format = url.searchParams.get("format") || "docx";
4969
+ try {
4970
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
4971
+ const doc = parseMarkdownDocument(mdContent);
4972
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
4973
+ const mergedConfig = { ...baseConfig, ...resolvedConfig };
4974
+ const fileBase = path7.basename(absoluteFilePath, path7.extname(absoluteFilePath));
4975
+ if (format === "docx") {
4976
+ const buffer = await buildDocxDocument(doc, mergedConfig, baseDir);
4977
+ res.writeHead(200, {
4978
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
4979
+ "Content-Disposition": `attachment; filename="${fileBase}.docx"`
4980
+ });
4981
+ res.end(buffer);
4982
+ return;
4983
+ } else if (format === "pdf") {
4984
+ const buffer = await buildPdfDocument(doc, mergedConfig, baseDir);
4985
+ res.writeHead(200, {
4986
+ "Content-Type": "application/pdf",
4987
+ "Content-Disposition": `attachment; filename="${fileBase}.pdf"`
4988
+ });
4989
+ res.end(buffer);
4990
+ return;
4991
+ } else {
4992
+ const html = await buildHtmlDocument(doc, mergedConfig, baseDir);
4993
+ res.writeHead(200, {
4994
+ "Content-Type": "text/html; charset=utf-8",
4995
+ "Content-Disposition": `attachment; filename="${fileBase}.html"`
4996
+ });
4997
+ res.end(html);
4998
+ return;
4999
+ }
5000
+ } catch (err) {
5001
+ const msg = err instanceof Error ? err.message : String(err);
5002
+ res.writeHead(500, { "Content-Type": "text/plain" });
5003
+ res.end(`Export failed: ${msg}`);
5004
+ return;
5005
+ }
5006
+ }
5007
+ if (url.pathname === "/document-content") {
5008
+ try {
5009
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5010
+ const doc = parseMarkdownDocument(mdContent);
5011
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5012
+ const html = await buildHtmlDocument(doc, { ...baseConfig, ...resolvedConfig }, baseDir);
5013
+ const injectedScript = `
5014
+ <script>
5015
+ (function() {
5016
+ var evtSource = new EventSource('/events');
5017
+ evtSource.addEventListener('reload', function() {
5018
+ var scrollPos = window.scrollY;
5019
+ sessionStorage.setItem('markforge_scroll', scrollPos);
5020
+ window.location.reload();
5021
+ });
5022
+ window.addEventListener('load', function() {
5023
+ var saved = sessionStorage.getItem('markforge_scroll');
5024
+ if (saved) {
5025
+ window.scrollTo(0, parseInt(saved, 10));
5026
+ }
5027
+ });
5028
+ })();
5029
+ </script>
5030
+ `;
5031
+ const finalHtml = html.replace("</body>", `${injectedScript}</body>`);
5032
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5033
+ res.end(finalHtml);
5034
+ } catch (err) {
5035
+ const msg = err instanceof Error ? err.message : String(err);
5036
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
5037
+ 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>`);
5038
+ }
5039
+ return;
5040
+ }
5041
+ if (url.pathname === "/" || url.pathname === "/index.html") {
5042
+ const fileName = path7.basename(absoluteFilePath);
5043
+ const initialContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5044
+ const appHtml = `<!DOCTYPE html>
5045
+ <html lang="en">
5046
+ <head>
5047
+ <meta charset="UTF-8">
5048
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5049
+ <title>MarkForge Live Studio - ${escapeHtml2(fileName)}</title>
5050
+ <link rel="preconnect" href="https://fonts.googleapis.com">
5051
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5052
+ <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">
5053
+ <style>
5054
+ :root {
5055
+ --mf-primary: #0D998D;
5056
+ --mf-primary-dark: #008277;
5057
+ --mf-primary-light: #ECFDFD;
5058
+ --mf-primary-border: #33CDCF;
5059
+ --mf-dark: #0F172A;
5060
+ --mf-slate: #1E293B;
5061
+ --mf-editor-bg: #0F172A;
5062
+ --mf-editor-gutter: #1E293B;
5063
+ --mf-editor-text: #F8FAFC;
5064
+ --mf-muted: #64748B;
5065
+ --mf-light-border: #E2E8F0;
5066
+ --mf-bg: #F1F5F9;
5067
+ }
5068
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5069
+ body {
5070
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5071
+ background: var(--mf-bg);
5072
+ color: var(--mf-dark);
5073
+ display: flex;
5074
+ flex-direction: column;
5075
+ height: 100vh;
5076
+ overflow: hidden;
5077
+ }
5078
+ header {
5079
+ background: #FFFFFF;
5080
+ border-bottom: 1px solid var(--mf-light-border);
5081
+ min-height: 56px;
5082
+ display: flex;
5083
+ flex-wrap: wrap;
5084
+ align-items: center;
5085
+ justify-content: space-between;
5086
+ padding: 0.4rem 1.2rem;
5087
+ z-index: 10;
5088
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
5089
+ gap: 0.75rem;
5090
+ }
5091
+ .brand-section {
5092
+ display: flex;
5093
+ align-items: center;
5094
+ gap: 0.75rem;
5095
+ }
5096
+ .brand-badge {
5097
+ font-size: 0.72rem;
5098
+ font-weight: 800;
5099
+ letter-spacing: 0.08em;
5100
+ background: var(--mf-dark);
5101
+ color: #FFFFFF;
5102
+ padding: 0.25rem 0.55rem;
5103
+ border-radius: 4px;
5104
+ text-transform: uppercase;
5105
+ }
5106
+ .file-name {
5107
+ font-size: 0.9rem;
5108
+ font-weight: 700;
5109
+ color: var(--mf-dark);
5110
+ }
5111
+ .sync-status {
5112
+ display: flex;
5113
+ align-items: center;
5114
+ gap: 0.35rem;
5115
+ font-size: 0.75rem;
5116
+ font-weight: 600;
5117
+ color: var(--mf-primary-dark);
5118
+ background: var(--mf-primary-light);
5119
+ padding: 0.2rem 0.55rem;
5120
+ border-radius: 9999px;
5121
+ border: 1px solid var(--mf-primary-border);
5122
+ }
5123
+ .sync-dot {
5124
+ width: 7px;
5125
+ height: 7px;
5126
+ background-color: var(--mf-primary);
5127
+ border-radius: 50%;
5128
+ box-shadow: 0 0 0 2px rgba(13, 153, 141, 0.2);
5129
+ }
5130
+ .toolbar-section {
5131
+ display: flex;
5132
+ align-items: center;
5133
+ gap: 0.3rem;
5134
+ background: #F8FAFC;
5135
+ padding: 0.25rem 0.4rem;
5136
+ border-radius: 6px;
5137
+ border: 1px solid var(--mf-light-border);
5138
+ }
5139
+ .tool-btn {
5140
+ font-family: 'JetBrains Mono', monospace;
5141
+ font-size: 0.75rem;
5142
+ font-weight: 600;
5143
+ padding: 0.25rem 0.45rem;
5144
+ background: transparent;
5145
+ border: 1px solid transparent;
5146
+ border-radius: 4px;
5147
+ cursor: pointer;
5148
+ color: var(--mf-slate);
5149
+ transition: all 0.1s ease;
5150
+ }
5151
+ .tool-btn:hover {
5152
+ background: #FFFFFF;
5153
+ border-color: var(--mf-light-border);
5154
+ color: var(--mf-primary-dark);
5155
+ }
5156
+ .tool-divider {
5157
+ width: 1px;
5158
+ height: 16px;
5159
+ background: var(--mf-light-border);
5160
+ margin: 0 0.15rem;
5161
+ }
5162
+ .controls {
5163
+ display: flex;
5164
+ align-items: center;
5165
+ gap: 0.5rem;
5166
+ }
5167
+ .view-toggles {
5168
+ display: flex;
5169
+ background: #F1F5F9;
5170
+ padding: 2px;
5171
+ border-radius: 6px;
5172
+ border: 1px solid var(--mf-light-border);
5173
+ }
5174
+ .toggle-btn {
5175
+ font-family: inherit;
5176
+ font-size: 0.74rem;
5177
+ font-weight: 600;
5178
+ padding: 0.25rem 0.55rem;
5179
+ border: none;
5180
+ background: transparent;
5181
+ border-radius: 4px;
5182
+ cursor: pointer;
5183
+ color: var(--mf-muted);
5184
+ transition: all 0.15s ease;
5185
+ }
5186
+ .toggle-btn.active {
5187
+ background: #FFFFFF;
5188
+ color: var(--mf-dark);
5189
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
5190
+ }
5191
+ .btn {
5192
+ font-family: inherit;
5193
+ font-size: 0.78rem;
5194
+ font-weight: 600;
5195
+ padding: 0.35rem 0.75rem;
5196
+ border-radius: 6px;
5197
+ cursor: pointer;
5198
+ text-decoration: none;
5199
+ transition: all 0.15s ease;
5200
+ display: inline-flex;
5201
+ align-items: center;
5202
+ gap: 0.3rem;
5203
+ border: 1px solid var(--mf-light-border);
5204
+ background: #FFFFFF;
5205
+ color: var(--mf-dark);
5206
+ }
5207
+ .btn:hover {
5208
+ background: #F8FAFC;
5209
+ border-color: #CBD5E1;
5210
+ }
5211
+ .btn-primary {
5212
+ background: var(--mf-primary);
5213
+ color: #FFFFFF;
5214
+ border-color: var(--mf-primary);
5215
+ }
5216
+ .btn-primary:hover {
5217
+ background: var(--mf-primary-dark);
5218
+ border-color: var(--mf-primary-dark);
5219
+ }
5220
+ .save-indicator {
5221
+ font-size: 0.75rem;
5222
+ font-weight: 600;
5223
+ color: var(--mf-muted);
5224
+ min-width: 65px;
5225
+ text-align: right;
5226
+ }
5227
+ .save-indicator.saved {
5228
+ color: var(--mf-primary-dark);
5229
+ }
5230
+ .save-indicator.saving {
5231
+ color: #D97706;
5232
+ }
5233
+ .save-indicator.unsaved {
5234
+ color: #E11D48;
5235
+ }
5236
+
5237
+ /* Main Workspace Splitter Layout */
5238
+ main.workspace {
5239
+ flex: 1;
5240
+ display: flex;
5241
+ height: calc(100vh - 56px);
5242
+ overflow: hidden;
5243
+ background: var(--mf-bg);
5244
+ position: relative;
5245
+ }
5246
+ .editor-pane {
5247
+ width: 50%;
5248
+ height: 100%;
5249
+ display: flex;
5250
+ flex-direction: column;
5251
+ background: var(--mf-editor-bg);
5252
+ border-right: 1px solid #334155;
5253
+ overflow: hidden;
5254
+ }
5255
+ .editor-header {
5256
+ background: #090D16;
5257
+ border-bottom: 1px solid #1E293B;
5258
+ padding: 0.4rem 0.8rem;
5259
+ display: flex;
5260
+ align-items: center;
5261
+ justify-content: space-between;
5262
+ color: #94A3B8;
5263
+ font-size: 0.74rem;
5264
+ font-weight: 500;
5265
+ }
5266
+ .editor-container {
5267
+ flex: 1;
5268
+ display: flex;
5269
+ position: relative;
5270
+ overflow: hidden;
5271
+ background: var(--mf-editor-bg);
5272
+ }
5273
+ .line-numbers {
5274
+ width: 44px;
5275
+ padding: 0.8rem 0.4rem;
5276
+ font-family: 'JetBrains Mono', monospace;
5277
+ font-size: 13px;
5278
+ line-height: 1.55;
5279
+ color: #475569;
5280
+ text-align: right;
5281
+ user-select: none;
5282
+ background: var(--mf-editor-gutter);
5283
+ overflow: hidden;
5284
+ border-right: 1px solid #1E293B;
5285
+ }
5286
+ .code-editor {
5287
+ flex: 1;
5288
+ padding: 0.8rem 1rem;
5289
+ font-family: 'JetBrains Mono', monospace;
5290
+ font-size: 13px;
5291
+ line-height: 1.55;
5292
+ color: var(--mf-editor-text);
5293
+ background: transparent;
5294
+ border: none;
5295
+ outline: none;
5296
+ resize: none;
5297
+ white-space: pre;
5298
+ overflow-wrap: normal;
5299
+ overflow: auto;
5300
+ tab-size: 2;
5301
+ }
5302
+
5303
+ /* Draggable Splitter Handle */
5304
+ .splitter {
5305
+ width: 8px;
5306
+ cursor: col-resize;
5307
+ background: #E2E8F0;
5308
+ transition: background 0.15s ease;
5309
+ position: relative;
5310
+ z-index: 5;
5311
+ }
5312
+ .splitter:hover, .splitter.active {
5313
+ background: var(--mf-primary);
5314
+ }
5315
+
5316
+ /* Right Preview Pane */
5317
+ .preview-pane {
5318
+ width: 50%;
5319
+ height: 100%;
5320
+ display: flex;
5321
+ flex-direction: column;
5322
+ background: #FFFFFF;
5323
+ overflow: hidden;
5324
+ }
5325
+ .preview-header {
5326
+ background: #FFFFFF;
5327
+ border-bottom: 1px solid var(--mf-light-border);
5328
+ padding: 0.35rem 0.8rem;
5329
+ display: flex;
5330
+ align-items: center;
5331
+ justify-content: space-between;
5332
+ color: var(--mf-muted);
5333
+ font-size: 0.74rem;
5334
+ font-weight: 600;
5335
+ }
5336
+ .viewport-selector {
5337
+ display: flex;
5338
+ gap: 0.25rem;
5339
+ }
5340
+ .vp-btn {
5341
+ font-size: 0.72rem;
5342
+ padding: 0.15rem 0.4rem;
5343
+ border: 1px solid var(--mf-light-border);
5344
+ background: #F8FAFC;
5345
+ border-radius: 4px;
5346
+ cursor: pointer;
5347
+ color: var(--mf-muted);
5348
+ }
5349
+ .vp-btn.active {
5350
+ background: var(--mf-primary-light);
5351
+ color: var(--mf-primary-dark);
5352
+ border-color: var(--mf-primary-border);
5353
+ }
5354
+ .preview-wrapper {
5355
+ flex: 1;
5356
+ display: flex;
5357
+ justify-content: center;
5358
+ align-items: stretch;
5359
+ background: #F1F5F9;
5360
+ overflow: hidden;
5361
+ }
5362
+ iframe {
5363
+ width: 100%;
5364
+ height: 100%;
5365
+ border: none;
5366
+ background: #FFFFFF;
5367
+ transition: max-width 0.2s ease;
5368
+ }
5369
+ .author-footer {
5370
+ font-size: 0.72rem;
5371
+ color: var(--mf-muted);
5372
+ padding-right: 0.5rem;
5373
+ }
5374
+ .author-footer a {
5375
+ color: var(--mf-primary-dark);
5376
+ text-decoration: none;
5377
+ font-weight: 600;
5378
+ }
5379
+ </style>
5380
+ </head>
5381
+ <body>
5382
+ <header>
5383
+ <div class="brand-section">
5384
+ <span class="brand-badge">MARKFORGE STUDIO</span>
5385
+ <span class="file-name" title="${escapeHtml2(absoluteFilePath)}">${escapeHtml2(fileName)}</span>
5386
+ <div class="sync-status">
5387
+ <div class="sync-dot"></div>
5388
+ <span>Live Sync Active</span>
5389
+ </div>
5390
+ </div>
5391
+
5392
+ <!-- Quick Formatting Toolbar -->
5393
+ <div class="toolbar-section">
5394
+ <button class="tool-btn" onclick="insertFormat('h1')" title="Heading 1">H1</button>
5395
+ <button class="tool-btn" onclick="insertFormat('h2')" title="Heading 2">H2</button>
5396
+ <button class="tool-btn" onclick="insertFormat('h3')" title="Heading 3">H3</button>
5397
+ <div class="tool-divider"></div>
5398
+ <button class="tool-btn" onclick="insertFormat('bold')" title="Bold">B</button>
5399
+ <button class="tool-btn" onclick="insertFormat('italic')" title="Italic">I</button>
5400
+ <button class="tool-btn" onclick="insertFormat('code')" title="Inline Code">&lt;&gt;</button>
5401
+ <button class="tool-btn" onclick="insertFormat('quote')" title="Blockquote">&gt;</button>
5402
+ <div class="tool-divider"></div>
5403
+ <button class="tool-btn" onclick="insertFormat('table')" title="GFM Table">Table</button>
5404
+ <button class="tool-btn" onclick="insertFormat('list')" title="List">List</button>
5405
+ <button class="tool-btn" onclick="insertFormat('task')" title="Task Checklist">Task</button>
5406
+ <div class="tool-divider"></div>
5407
+ <button class="tool-btn" onclick="insertFormat('callout')" title="Callout Box">Callout</button>
5408
+ <button class="tool-btn" onclick="insertFormat('math')" title="LaTeX Math">Math</button>
5409
+ <button class="tool-btn" onclick="insertFormat('columns')" title="Multi-Columns">Columns</button>
5410
+ <button class="tool-btn" onclick="insertFormat('footnote')" title="Footnote">Footnote</button>
5411
+ <button class="tool-btn" onclick="insertFormat('mermaid')" title="Mermaid Diagram">Mermaid</button>
5412
+ </div>
5413
+
5414
+ <!-- Controls & View Mode -->
5415
+ <div class="controls">
5416
+ <div class="view-toggles">
5417
+ <button class="toggle-btn active" id="btn-split" onclick="setViewMode('split')">Split</button>
5418
+ <button class="toggle-btn" id="btn-edit" onclick="setViewMode('edit')">Editor</button>
5419
+ <button class="toggle-btn" id="btn-prev" onclick="setViewMode('prev')">Preview</button>
5420
+ </div>
5421
+ <span class="save-indicator saved" id="save-status">Saved</span>
5422
+ <button class="btn btn-primary" onclick="saveContentManual()" title="Save (Ctrl+S)">Save</button>
5423
+ <button class="btn" onclick="exportDoc('docx')" title="Download Word Document">DOCX</button>
5424
+ <button class="btn" onclick="exportDoc('pdf')" title="Download PDF Document">PDF</button>
5425
+ <button class="btn" onclick="printDoc()" title="Print / PDF dialog">Print</button>
5426
+ </div>
5427
+ </header>
5428
+
5429
+ <main class="workspace" id="workspace">
5430
+ <!-- Left: Code Editor Pane -->
5431
+ <div class="editor-pane" id="editor-pane">
5432
+ <div class="editor-header">
5433
+ <span>MARKDOWN SOURCE</span>
5434
+ <span id="editor-stats">Lines: 1 | Words: 0 | UTF-8</span>
5435
+ </div>
5436
+ <div class="editor-container">
5437
+ <div class="line-numbers" id="line-numbers">1</div>
5438
+ <textarea class="code-editor" id="code-editor" spellcheck="false" placeholder="Write markdown here...">${escapeHtml2(initialContent)}</textarea>
5439
+ </div>
5440
+ </div>
5441
+
5442
+ <!-- Middle: Draggable Splitter Handle -->
5443
+ <div class="splitter" id="splitter"></div>
5444
+
5445
+ <!-- Right: Rendered Preview Pane -->
5446
+ <div class="preview-pane" id="preview-pane">
5447
+ <div class="preview-header">
5448
+ <span>RENDERED PREVIEW</span>
5449
+ <div class="viewport-selector">
5450
+ <button class="vp-btn active" onclick="setViewport('100%')" id="vp-full">100% Full</button>
5451
+ <button class="vp-btn" onclick="setViewport('820px')" id="vp-a4">A4 (820px)</button>
5452
+ <button class="vp-btn" onclick="setViewport('440px')" id="vp-mob">Mobile</button>
5453
+ </div>
5454
+ <span class="author-footer">Created by <a href="https://github.com/masumrpg" target="_blank">Ma'sum (@masumrpg)</a></span>
5455
+ </div>
5456
+ <div class="preview-wrapper">
5457
+ <iframe id="preview-frame" src="/document-content"></iframe>
5458
+ </div>
5459
+ </div>
5460
+ </main>
5461
+
5462
+ <script>
5463
+ var editor = document.getElementById('code-editor');
5464
+ var lineNumbers = document.getElementById('line-numbers');
5465
+ var stats = document.getElementById('editor-stats');
5466
+ var saveStatus = document.getElementById('save-status');
5467
+ var previewFrame = document.getElementById('preview-frame');
5468
+ var editorPane = document.getElementById('editor-pane');
5469
+ var previewPane = document.getElementById('preview-pane');
5470
+ var splitter = document.getElementById('splitter');
5471
+ var isDirty = false;
5472
+ var autoSaveTimeout = null;
5473
+
5474
+ // Update Line Numbers & Stats
5475
+ function updateStatsAndLines() {
5476
+ var lines = editor.value.split('\\n');
5477
+ var lineCount = lines.length;
5478
+ var numHtml = '';
5479
+ for (var i = 1; i <= lineCount; i++) {
5480
+ numHtml += i + '<br>';
5481
+ }
5482
+ lineNumbers.innerHTML = numHtml;
5483
+
5484
+ var words = editor.value.trim().length > 0 ? editor.value.trim().split(/\\s+/).length : 0;
5485
+ var chars = editor.value.length;
5486
+ stats.textContent = 'Lines: ' + lineCount + ' | Words: ' + words + ' | Chars: ' + chars + ' | UTF-8';
5487
+ }
5488
+
5489
+ // Synchronize vertical scroll between Line Numbers and Textarea
5490
+ editor.addEventListener('scroll', function() {
5491
+ lineNumbers.scrollTop = editor.scrollTop;
5492
+ });
5493
+
5494
+ // Handle Input & Debounced Auto-Save
5495
+ editor.addEventListener('input', function() {
5496
+ updateStatsAndLines();
5497
+ setSaveState('unsaved');
5498
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5499
+ autoSaveTimeout = setTimeout(function() {
5500
+ saveContent();
5501
+ }, 600);
5502
+ });
5503
+
5504
+ function setSaveState(state) {
5505
+ if (state === 'saved') {
5506
+ saveStatus.textContent = 'Saved';
5507
+ saveStatus.className = 'save-indicator saved';
5508
+ isDirty = false;
5509
+ } else if (state === 'saving') {
5510
+ saveStatus.textContent = 'Saving...';
5511
+ saveStatus.className = 'save-indicator saving';
5512
+ } else {
5513
+ saveStatus.textContent = 'Changes...';
5514
+ saveStatus.className = 'save-indicator unsaved';
5515
+ isDirty = true;
5516
+ }
5517
+ }
5518
+
5519
+ // Save Content via API
5520
+ function saveContent(callback) {
5521
+ setSaveState('saving');
5522
+ fetch('/api/save-content', {
5523
+ method: 'POST',
5524
+ headers: { 'Content-Type': 'application/json' },
5525
+ body: JSON.stringify({ content: editor.value }),
5526
+ })
5527
+ .then(function(res) { return res.json(); })
5528
+ .then(function(data) {
5529
+ if (data.success) {
5530
+ setSaveState('saved');
5531
+ if (callback) callback();
5532
+ } else {
5533
+ saveStatus.textContent = 'Save Error';
5534
+ }
5535
+ })
5536
+ .catch(function() {
5537
+ saveStatus.textContent = 'Save Error';
5538
+ });
5539
+ }
5540
+
5541
+ function saveContentManual() {
5542
+ saveContent();
5543
+ }
5544
+
5545
+ // Keyboard Shortcuts: Tab (2 spaces), Shift+Tab, Ctrl+S
5546
+ editor.addEventListener('keydown', function(e) {
5547
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
5548
+ e.preventDefault();
5549
+ saveContent();
5550
+ return;
5551
+ }
5552
+
5553
+ if (e.key === 'Tab') {
5554
+ e.preventDefault();
5555
+ var start = this.selectionStart;
5556
+ var end = this.selectionEnd;
5557
+ this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
5558
+ this.selectionStart = this.selectionEnd = start + 2;
5559
+ updateStatsAndLines();
5560
+ setSaveState('unsaved');
5561
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5562
+ autoSaveTimeout = setTimeout(saveContent, 600);
5563
+ }
5564
+ });
5565
+
5566
+ // Formatting Snippet Injector
5567
+ function insertFormat(type) {
5568
+ var start = editor.selectionStart;
5569
+ var end = editor.selectionEnd;
5570
+ var selected = editor.value.substring(start, end);
5571
+ var replacement = '';
5572
+
5573
+ switch (type) {
5574
+ case 'h1': replacement = '# ' + (selected || 'Heading 1'); break;
5575
+ case 'h2': replacement = '## ' + (selected || 'Heading 2'); break;
5576
+ case 'h3': replacement = '### ' + (selected || 'Heading 3'); break;
5577
+ case 'bold': replacement = '**' + (selected || 'bold text') + '**'; break;
5578
+ case 'italic': replacement = '*' + (selected || 'italic text') + '*'; break;
5579
+ case 'code': replacement = '\`' + (selected || 'inline code') + '\`'; break;
5580
+ case 'quote': replacement = '> ' + (selected || 'Quote text'); break;
5581
+ case 'table':
5582
+ replacement = '\\n| Column 1 | Column 2 | Column 3 |\\n| :--- | :---: | ---: |\\n| Data A | Data B | Data C |\\n| Data D | Data E | Data F |\\n';
5583
+ break;
5584
+ case 'list': replacement = '- ' + (selected || 'List item'); break;
5585
+ case 'task': replacement = '- [ ] ' + (selected || 'Task item'); break;
5586
+ case 'callout':
5587
+ replacement = '> [!NOTE]\\n> ' + (selected || 'This is an important callout note.');
5588
+ break;
5589
+ case 'math':
5590
+ replacement = '$$\\n' + (selected || '\\\\int_{-\\\\infty}^{\\\\infty} e^{-x^2} dx = \\\\sqrt{\\\\pi}') + '\\n$$';
5591
+ break;
5592
+ case 'columns':
5593
+ 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:::';
5594
+ break;
5595
+ case 'footnote':
5596
+ replacement = (selected || 'Statement with footnote') + '[^1]\\n\\n[^1]: Note description text.';
5597
+ break;
5598
+ case 'mermaid':
5599
+ 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';
5600
+ break;
5601
+ }
5602
+
5603
+ editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
5604
+ editor.selectionStart = editor.selectionEnd = start + replacement.length;
5605
+ editor.focus();
5606
+ updateStatsAndLines();
5607
+ setSaveState('unsaved');
5608
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5609
+ autoSaveTimeout = setTimeout(saveContent, 600);
5610
+ }
5611
+
5612
+ // View Mode Toggle (Split / Editor Only / Preview Only)
5613
+ function setViewMode(mode) {
5614
+ document.getElementById('btn-split').classList.remove('active');
5615
+ document.getElementById('btn-edit').classList.remove('active');
5616
+ document.getElementById('btn-prev').classList.remove('active');
5617
+
5618
+ if (mode === 'split') {
5619
+ document.getElementById('btn-split').classList.add('active');
5620
+ editorPane.style.display = 'flex';
5621
+ editorPane.style.width = '50%';
5622
+ previewPane.style.display = 'flex';
5623
+ previewPane.style.width = '50%';
5624
+ splitter.style.display = 'block';
5625
+ } else if (mode === 'edit') {
5626
+ document.getElementById('btn-edit').classList.add('active');
5627
+ editorPane.style.display = 'flex';
5628
+ editorPane.style.width = '100%';
5629
+ previewPane.style.display = 'none';
5630
+ splitter.style.display = 'none';
5631
+ } else if (mode === 'prev') {
5632
+ document.getElementById('btn-prev').classList.add('active');
5633
+ editorPane.style.display = 'none';
5634
+ previewPane.style.display = 'flex';
5635
+ previewPane.style.width = '100%';
5636
+ splitter.style.display = 'none';
5637
+ }
5638
+ }
5639
+
5640
+ // Viewport Width Resizer
5641
+ function setViewport(width) {
5642
+ document.getElementById('vp-full').classList.remove('active');
5643
+ document.getElementById('vp-a4').classList.remove('active');
5644
+ document.getElementById('vp-mob').classList.remove('active');
5645
+
5646
+ if (width === '100%') {
5647
+ document.getElementById('vp-full').classList.add('active');
5648
+ previewFrame.style.maxWidth = '100%';
5649
+ } else if (width === '820px') {
5650
+ document.getElementById('vp-a4').classList.add('active');
5651
+ previewFrame.style.maxWidth = '820px';
5652
+ } else if (width === '440px') {
5653
+ document.getElementById('vp-mob').classList.add('active');
5654
+ previewFrame.style.maxWidth = '440px';
5655
+ }
5656
+ }
5657
+
5658
+ // Draggable Splitter Handle Logic
5659
+ var isDragging = false;
5660
+ splitter.addEventListener('mousedown', function(e) {
5661
+ isDragging = true;
5662
+ splitter.classList.add('active');
5663
+ document.body.style.cursor = 'col-resize';
5664
+ document.body.style.userSelect = 'none';
5665
+ });
5666
+
5667
+ window.addEventListener('mousemove', function(e) {
5668
+ if (!isDragging) return;
5669
+ var totalWidth = document.getElementById('workspace').clientWidth;
5670
+ var newEditorWidth = (e.clientX / totalWidth) * 100;
5671
+ if (newEditorWidth > 15 && newEditorWidth < 85) {
5672
+ editorPane.style.width = newEditorWidth + '%';
5673
+ previewPane.style.width = (100 - newEditorWidth) + '%';
5674
+ }
5675
+ });
5676
+
5677
+ window.addEventListener('mouseup', function() {
5678
+ if (isDragging) {
5679
+ isDragging = false;
5680
+ splitter.classList.remove('active');
5681
+ document.body.style.cursor = '';
5682
+ document.body.style.userSelect = '';
5683
+ }
5684
+ });
5685
+
5686
+ // Document Print Action
5687
+ function printDoc() {
5688
+ previewFrame.contentWindow.print();
5689
+ }
5690
+
5691
+ // Document Export Action
5692
+ function exportDoc(format) {
5693
+ saveContent(function() {
5694
+ window.location.href = '/api/export?format=' + format;
5695
+ });
5696
+ }
5697
+
5698
+ // Initialize line stats
5699
+ updateStatsAndLines();
5700
+ </script>
5701
+ </body>
5702
+ </html>`;
5703
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5704
+ res.end(appHtml);
5705
+ return;
5706
+ }
5707
+ res.writeHead(404, { "Content-Type": "text/plain" });
5708
+ res.end("Not Found");
5709
+ });
5710
+ return new Promise((resolve6, reject) => {
5711
+ server.listen(port, () => {
5712
+ const url = `http://localhost:${port}`;
5713
+ resolve6({
5714
+ server,
5715
+ port,
5716
+ url,
5717
+ close: async () => {
5718
+ watcher.close();
5719
+ sseClients.forEach((client) => {
5720
+ try {
5721
+ client.end();
5722
+ } catch {
5723
+ }
5724
+ });
5725
+ sseClients.clear();
5726
+ return new Promise((res) => {
5727
+ server.close(() => res());
5728
+ });
5729
+ }
5730
+ });
5731
+ });
5732
+ server.on("error", (err) => {
5733
+ reject(err);
5734
+ });
5735
+ });
5736
+ }
5737
+ function escapeHtml2(str) {
5738
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5739
+ }
5740
+
3490
5741
  // src/config/defineConfig.ts
3491
5742
  function defineConfig(config) {
3492
5743
  return config;
3493
5744
  }
3494
5745
 
3495
5746
  // src/version.ts
3496
- import * as fs7 from "fs";
3497
- import * as path7 from "path";
5747
+ import * as fs8 from "fs";
5748
+ import * as path8 from "path";
3498
5749
  import { fileURLToPath } from "url";
3499
5750
  try {
3500
5751
  if (typeof globalThis !== "undefined" && (!globalThis.localStorage || typeof globalThis.localStorage.getItem !== "function")) {
@@ -3516,21 +5767,21 @@ try {
3516
5767
  }
3517
5768
  } catch {
3518
5769
  }
3519
- var FALLBACK_VERSION = "0.3.0";
5770
+ var FALLBACK_VERSION = "0.4.0";
3520
5771
  function readVersionFromPackageJson(fromDir) {
3521
5772
  let currentDir = fromDir;
3522
5773
  for (let i = 0; i < 6; i++) {
3523
5774
  try {
3524
- const pkgJsonPath = path7.join(currentDir, "package.json");
3525
- if (fs7.existsSync(pkgJsonPath)) {
3526
- const pkg = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5775
+ const pkgJsonPath = path8.join(currentDir, "package.json");
5776
+ if (fs8.existsSync(pkgJsonPath)) {
5777
+ const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
3527
5778
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
3528
5779
  return pkg.version;
3529
5780
  }
3530
5781
  }
3531
5782
  } catch {
3532
5783
  }
3533
- const parentDir = path7.dirname(currentDir);
5784
+ const parentDir = path8.dirname(currentDir);
3534
5785
  if (parentDir === currentDir) break;
3535
5786
  currentDir = parentDir;
3536
5787
  }
@@ -3541,7 +5792,7 @@ function getPackageDir() {
3541
5792
  return __dirname;
3542
5793
  }
3543
5794
  try {
3544
- return path7.dirname(fileURLToPath(import.meta.url));
5795
+ return path8.dirname(fileURLToPath(import.meta.url));
3545
5796
  } catch {
3546
5797
  return process.cwd();
3547
5798
  }
@@ -3552,6 +5803,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3552
5803
  }
3553
5804
  export {
3554
5805
  DEFAULT_CONFIG,
5806
+ KATEX_INLINE_CSS,
3555
5807
  MARKFORGE_VERSION,
3556
5808
  Orientation,
3557
5809
  OutputFormat,
@@ -3580,18 +5832,28 @@ export {
3580
5832
  inlineHtmlImages,
3581
5833
  loadConfig,
3582
5834
  compileMarkdown as markforge,
5835
+ normalizeBackCover,
5836
+ normalizeCoverPage,
3583
5837
  normalizeHeaderFooter,
3584
5838
  normalizeHeaderFooterSlot,
5839
+ normalizeNumberHeadings,
5840
+ normalizeSecurity,
3585
5841
  normalizeSignatures,
3586
5842
  normalizeWatermark,
3587
5843
  parseInlineSpans,
3588
5844
  parseMarginToTwip2 as parseMarginToTwip,
5845
+ parseMarkdownDocument as parseMarkdown,
3589
5846
  parseMarkdownDocument,
5847
+ renderBackCoverHtml,
5848
+ renderCoverPageHtml,
3590
5849
  renderInlinesToHtml,
5850
+ renderMathToHtml,
3591
5851
  renderMermaidToPng,
5852
+ renderNodesToHtml,
3592
5853
  replaceDocumentTokens,
3593
5854
  resolveDocumentConfig,
3594
5855
  resolveImage,
3595
5856
  slugify,
5857
+ startPreviewServer,
3596
5858
  tokenizeCodeLine
3597
5859
  };