@masumdev/markforge 0.2.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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) {
@@ -1347,6 +1535,190 @@ function normalizeHeaderFooter(raw, meta) {
1347
1535
  dividerColor: raw.dividerColor || "#E2E8F0"
1348
1536
  };
1349
1537
  }
1538
+ function normalizeSignatures(raw, meta = {}) {
1539
+ if (!raw) return void 0;
1540
+ let rawItems = [];
1541
+ let rawConfig = {};
1542
+ if (Array.isArray(raw)) {
1543
+ rawItems = raw;
1544
+ } else if (typeof raw === "object" && Array.isArray(raw.items)) {
1545
+ rawItems = raw.items;
1546
+ rawConfig = raw;
1547
+ }
1548
+ if (rawItems.length === 0) return void 0;
1549
+ const cappedItems = rawItems.slice(0, 4);
1550
+ const items = cappedItems.map((item) => {
1551
+ const tokenCtx = meta;
1552
+ const rawName = typeof item.name === "string" ? item.name : "";
1553
+ const name = replaceDocumentTokens(rawName, tokenCtx).trim();
1554
+ const title = item.title ? replaceDocumentTokens(item.title, tokenCtx).trim() : void 0;
1555
+ const role = item.role ? replaceDocumentTokens(item.role, tokenCtx).trim() : void 0;
1556
+ let dateStr;
1557
+ if (typeof item.date === "string") {
1558
+ dateStr = replaceDocumentTokens(item.date, tokenCtx).trim();
1559
+ } else if (item.date === true) {
1560
+ dateStr = meta.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1561
+ }
1562
+ let signatureHeight = 60;
1563
+ if (typeof item.signatureHeight === "number") {
1564
+ signatureHeight = item.signatureHeight;
1565
+ } else if (typeof item.signatureHeight === "string") {
1566
+ const parsed = parseFloat(item.signatureHeight);
1567
+ if (!isNaN(parsed)) signatureHeight = parsed;
1568
+ }
1569
+ return {
1570
+ title,
1571
+ name: name || "Authorized Signatory",
1572
+ role,
1573
+ date: dateStr,
1574
+ image: item.image,
1575
+ signatureHeight
1576
+ };
1577
+ });
1578
+ const align = rawConfig.align || (items.length === 1 ? "right" : "space-between");
1579
+ const style = rawConfig.style || "line";
1580
+ const borderColor = rawConfig.borderColor || "#CBD5E1";
1581
+ const titleColor = rawConfig.titleColor || "#64748B";
1582
+ const nameColor = rawConfig.nameColor || "#0F172A";
1583
+ const roleColor = rawConfig.roleColor || "#64748B";
1584
+ const spacingBeforeRaw = rawConfig.spacingBefore ?? "2.5rem";
1585
+ const spacingBefore = formatMarginCss(spacingBeforeRaw, "2.5rem");
1586
+ const spacingBeforeTwip = parseMarginToTwip(spacingBeforeRaw, 600);
1587
+ return {
1588
+ items,
1589
+ align,
1590
+ style,
1591
+ borderColor,
1592
+ titleColor,
1593
+ nameColor,
1594
+ roleColor,
1595
+ spacingBefore,
1596
+ spacingBeforeTwip
1597
+ };
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
+ }
1350
1722
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1351
1723
  const configMeta = userConfig.metadata || {};
1352
1724
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1387,6 +1759,17 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1387
1759
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1388
1760
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1389
1761
  const watermark = normalizeWatermark(rawWatermark);
1762
+ const rawSignatures = mergedMeta.signatures || userConfig.signatures;
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;
1390
1773
  const cssList = [];
1391
1774
  const addCss = (item) => {
1392
1775
  if (!item) return;
@@ -1414,7 +1797,13 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1414
1797
  header,
1415
1798
  footer,
1416
1799
  toc,
1800
+ signatures,
1417
1801
  watermark,
1802
+ coverPage,
1803
+ backCover,
1804
+ numberHeadings,
1805
+ security,
1806
+ math,
1418
1807
  css: cssList,
1419
1808
  embedImages,
1420
1809
  bundleHtml,
@@ -1422,6 +1811,45 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1422
1811
  };
1423
1812
  }
1424
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
+
1425
1853
  // src/core/html/htmlBuilder.ts
1426
1854
  function escapeHtml(str) {
1427
1855
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -1464,6 +1892,15 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1464
1892
  result += `<code>${escapeHtml(span.content)}</code>`;
1465
1893
  continue;
1466
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
+ }
1467
1904
  if (span.type === "htmlInline") {
1468
1905
  result += span.content;
1469
1906
  continue;
@@ -1472,66 +1909,9 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1472
1909
  }
1473
1910
  return result;
1474
1911
  }
1475
- async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1476
- const resolved = resolveDocumentConfig(doc.metadata, config);
1477
- const baseThemeCss = generateThemeCss(resolved.theme);
1478
- let customCss = "";
1479
- for (const cssPath of resolved.css) {
1480
- const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
1481
- if (fs3.existsSync(fullCssPath)) {
1482
- customCss += `
1483
- /* Custom CSS: ${cssPath} */
1484
- ` + fs3.readFileSync(fullCssPath, "utf-8");
1485
- }
1486
- }
1487
- const inlinedCss = doc.inlinedStyles.join("\n");
1912
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1488
1913
  let bodyHtml = "";
1489
- if (resolved.title) {
1490
- bodyHtml += ` <header class="document-header">
1491
- `;
1492
- bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1493
- `;
1494
- if (resolved.subtitle) {
1495
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1496
- `;
1497
- }
1498
- if (resolved.author || resolved.date || resolved.version) {
1499
- bodyHtml += ` <div class="document-meta">
1500
- `;
1501
- if (resolved.author) {
1502
- bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1503
- `;
1504
- }
1505
- if (resolved.version) {
1506
- bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1507
- `;
1508
- }
1509
- if (resolved.date) {
1510
- bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1511
- `;
1512
- }
1513
- bodyHtml += ` </div>
1514
- `;
1515
- }
1516
- bodyHtml += ` </header>
1517
- `;
1518
- }
1519
- if (resolved.toc && doc.tocEntries.length > 0) {
1520
- bodyHtml += ` <nav class="table-of-contents">
1521
- `;
1522
- bodyHtml += ` <h2>Table of Contents</h2>
1523
- <ul>
1524
- `;
1525
- for (const entry of doc.tocEntries) {
1526
- const indent = " ".repeat(entry.level);
1527
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1528
- `;
1529
- }
1530
- bodyHtml += ` </ul>
1531
- </nav>
1532
- `;
1533
- }
1534
- for (const node of doc.nodes) {
1914
+ for (const node of nodes) {
1535
1915
  if (node.type === "heading") {
1536
1916
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1537
1917
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
@@ -1541,6 +1921,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1541
1921
  if (node.type === "paragraph") {
1542
1922
  const inner = await renderInlinesToHtml(node.inlines, baseDir);
1543
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>
1544
1944
  `;
1545
1945
  continue;
1546
1946
  }
@@ -1629,87 +2029,734 @@ ${escapeHtml(node.text || "")}
1629
2029
  continue;
1630
2030
  }
1631
2031
  }
1632
- let watermarkCss = "";
1633
- let watermarkHtml = "";
1634
- if (resolved.watermark) {
1635
- const wm = resolved.watermark;
1636
- watermarkCss = `
1637
- .document-watermark {
1638
- position: fixed;
1639
- top: 50%;
1640
- left: 50%;
1641
- transform: translate(-50%, -50%) rotate(${wm.rotate}deg);
1642
- font-size: ${wm.fontSize}pt;
1643
- font-weight: 900;
1644
- color: ${wm.color};
1645
- opacity: ${wm.opacity};
1646
- pointer-events: none;
1647
- z-index: 0;
1648
- user-select: none;
1649
- text-transform: uppercase;
1650
- letter-spacing: 0.15em;
1651
- white-space: nowrap;
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>`;
1652
2041
  }
1653
- .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;
1654
2062
  position: relative;
1655
- 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;
2141
+ }
2142
+ @media print {
2143
+ .markforge-cover {
2144
+ page-break-after: always;
2145
+ break-after: page;
2146
+ height: 100vh;
2147
+ min-height: 100vh;
2148
+ max-height: 100vh;
2149
+ box-sizing: border-box;
2150
+ overflow: hidden;
2151
+ margin: 0;
2152
+ -webkit-print-color-adjust: exact;
2153
+ print-color-adjust: exact;
2154
+ }
1656
2155
  }
1657
2156
  `;
1658
- watermarkHtml = ` <div class="document-watermark">${escapeHtml(wm.text)}</div>
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>
1659
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>`;
1660
2181
  }
1661
- const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
1662
- const mermaidScript = hasMermaid ? `<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
1663
- <script>
1664
- mermaid.initialize({
1665
- startOnLoad: true,
1666
- theme: 'neutral',
1667
- themeVariables: {
1668
- primaryColor: '#33CDCF',
1669
- primaryTextColor: '#0F172A',
1670
- primaryBorderColor: '#009DA0',
1671
- lineColor: '#009DA0',
1672
- secondaryColor: '#ECFDFD',
1673
- tertiaryColor: '#F8FAFC'
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>`);
1674
2195
  }
1675
- });
1676
- </script>` : "";
1677
- return `<!DOCTYPE html>
1678
- <html lang="${resolved.lang}">
1679
- <head>
1680
- <meta charset="UTF-8">
1681
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1682
- <title>${escapeHtml(resolved.title)}</title>
1683
- <style>
1684
- ${THEME_COMPONENTS}
1685
- ${baseThemeCss}
1686
- ${customCss}
1687
- ${inlinedCss}
1688
- ${watermarkCss}
1689
- </style>
1690
- </head>
1691
- <body>
1692
- ${watermarkHtml} <div class="document-container">
1693
- ${bodyHtml} </div>
1694
- ${mermaidScript}
1695
- </body>
1696
- </html>`;
1697
- }
1698
-
1699
- // src/core/pdf/pdfBuilder.ts
1700
- function findChromeExecutable() {
1701
- if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1702
- return process.env.CHROME_PATH;
2196
+ }
1703
2197
  }
1704
- if (process.env.PUPPETEER_EXECUTABLE_PATH && fs4.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
1705
- return process.env.PUPPETEER_EXECUTABLE_PATH;
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")};
1706
2215
  }
1707
- const isWin = process.platform === "win32";
1708
- const winLocalAppData = process.env.LOCALAPPDATA ?? "";
1709
- const winProgramFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
1710
- const winProgramFilesX86 = process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
1711
- const candidates = [
1712
- // Linux
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;" : ""}
2548
+ display: flex;
2549
+ flex-direction: column;
2550
+ ${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
2551
+ }
2552
+ .markforge-sig-title {
2553
+ font-size: 0.85rem;
2554
+ color: ${sig.titleColor};
2555
+ font-weight: 600;
2556
+ margin-bottom: 6px;
2557
+ }
2558
+ .markforge-sig-space {
2559
+ height: var(--sig-height, 60px);
2560
+ display: flex;
2561
+ align-items: center;
2562
+ justify-content: center;
2563
+ margin-bottom: 6px;
2564
+ }
2565
+ .markforge-sig-space img {
2566
+ max-height: 100%;
2567
+ max-width: 100%;
2568
+ object-fit: contain;
2569
+ }
2570
+ .markforge-sig-line {
2571
+ ${sig.style === "line" ? `border-bottom: 1.5px solid ${sig.borderColor}; margin-bottom: 8px;` : ""}
2572
+ }
2573
+ .markforge-sig-name {
2574
+ font-size: 0.95rem;
2575
+ font-weight: 700;
2576
+ color: ${sig.nameColor};
2577
+ }
2578
+ .markforge-sig-role {
2579
+ font-size: 0.82rem;
2580
+ color: ${sig.roleColor};
2581
+ margin-top: 2px;
2582
+ }
2583
+ .markforge-sig-date {
2584
+ font-size: 0.78rem;
2585
+ color: ${sig.roleColor};
2586
+ margin-top: 2px;
2587
+ }
2588
+ @media print {
2589
+ .markforge-signatures {
2590
+ page-break-inside: avoid;
2591
+ break-inside: avoid;
2592
+ }
2593
+ }
2594
+ `;
2595
+ const itemCards = sig.items.map((item) => {
2596
+ const titleHtml = item.title ? `<div class="markforge-sig-title">${escapeHtml(item.title)}</div>` : "";
2597
+ let signSpaceHtml = "";
2598
+ if (item.image) {
2599
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"><img src="${escapeHtml(item.image)}" alt="Signature" /></div>`;
2600
+ } else {
2601
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"></div>`;
2602
+ }
2603
+ const lineHtml = sig.style === "line" ? `<div class="markforge-sig-line"></div>` : "";
2604
+ const nameHtml = `<div class="markforge-sig-name">${escapeHtml(item.name)}</div>`;
2605
+ const roleHtml = item.role ? `<div class="markforge-sig-role">${escapeHtml(item.role)}</div>` : "";
2606
+ const dateHtml = item.date ? `<div class="markforge-sig-date">Date: ${escapeHtml(item.date)}</div>` : "";
2607
+ return ` <div class="markforge-signature-card">
2608
+ ${titleHtml}
2609
+ ${signSpaceHtml}
2610
+ ${lineHtml}
2611
+ ${nameHtml}
2612
+ ${roleHtml}
2613
+ ${dateHtml}
2614
+ </div>`;
2615
+ }).join("\n");
2616
+ signaturesHtml = `
2617
+ <div class="markforge-signatures">
2618
+ ${itemCards}
2619
+ </div>
2620
+ `;
2621
+ }
2622
+ const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
2623
+ const mermaidScript = hasMermaid ? `<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
2624
+ <script>
2625
+ mermaid.initialize({
2626
+ startOnLoad: true,
2627
+ theme: 'neutral',
2628
+ themeVariables: {
2629
+ primaryColor: '#33CDCF',
2630
+ primaryTextColor: '#0F172A',
2631
+ primaryBorderColor: '#009DA0',
2632
+ lineColor: '#009DA0',
2633
+ secondaryColor: '#ECFDFD',
2634
+ tertiaryColor: '#F8FAFC'
2635
+ }
2636
+ });
2637
+ </script>` : "";
2638
+ return `<!DOCTYPE html>
2639
+ <html lang="${resolved.lang}">
2640
+ <head>
2641
+ <meta charset="UTF-8">
2642
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2643
+ <title>${escapeHtml(resolved.title)}</title>
2644
+ <style>
2645
+ ${THEME_COMPONENTS}
2646
+ ${baseThemeCss}
2647
+ ${KATEX_INLINE_CSS}
2648
+ ${extraCss}
2649
+ ${coverCss}
2650
+ ${backCss}
2651
+ ${customCss}
2652
+ ${inlinedCss}
2653
+ ${watermarkCss}
2654
+ ${signaturesCss}
2655
+ </style>
2656
+ </head>
2657
+ <body>
2658
+ ${watermarkHtml}${coverHtml} <div class="document-container">
2659
+ ${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
2660
+ ${mermaidScript}
2661
+ ${backHtml}</body>
2662
+ </html>`;
2663
+ }
2664
+
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
+ }
2747
+ function findChromeExecutable() {
2748
+ if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
2749
+ return process.env.CHROME_PATH;
2750
+ }
2751
+ if (process.env.PUPPETEER_EXECUTABLE_PATH && fs4.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
2752
+ return process.env.PUPPETEER_EXECUTABLE_PATH;
2753
+ }
2754
+ const isWin = process.platform === "win32";
2755
+ const winLocalAppData = process.env.LOCALAPPDATA ?? "";
2756
+ const winProgramFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
2757
+ const winProgramFilesX86 = process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
2758
+ const candidates = [
2759
+ // Linux
1713
2760
  "/usr/bin/google-chrome",
1714
2761
  "/usr/bin/google-chrome-stable",
1715
2762
  "/usr/bin/chromium",
@@ -1761,7 +2808,7 @@ function findChromeExecutable() {
1761
2808
  return null;
1762
2809
  }
1763
2810
  function injectPagedMediaStyles(html, config, metadata) {
1764
- var _a, _b, _c, _d, _e, _f;
2811
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1765
2812
  const resolved = resolveDocumentConfig(metadata || {}, config);
1766
2813
  const size = resolved.paperSize;
1767
2814
  const orientation = resolved.orientation;
@@ -1805,6 +2852,38 @@ function injectPagedMediaStyles(html, config, metadata) {
1805
2852
  ${fontStyle}
1806
2853
  }`;
1807
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
+ }` : "";
1808
2887
  const pagedCss = `
1809
2888
  @page {
1810
2889
  size: ${size} ${orientation};
@@ -1812,15 +2891,18 @@ function injectPagedMediaStyles(html, config, metadata) {
1812
2891
  margin-bottom: ${bottom};
1813
2892
  margin-left: ${left};
1814
2893
  margin-right: ${right};
1815
- ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
1816
- ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
1817
- ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
1818
- ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
1819
- ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
1820
- ${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)}
1821
2900
  }
2901
+ ${coverPageCss}
2902
+ ${backCoverCss}
1822
2903
  @media print {
1823
2904
  body { padding: 0; }
2905
+ .document-watermark { display: none !important; }
1824
2906
  h1, h2, h3, pre, table, blockquote, .callout {
1825
2907
  break-inside: avoid;
1826
2908
  }
@@ -1868,6 +2950,7 @@ startxref
1868
2950
  return Buffer.from(pdfBody, "utf-8");
1869
2951
  }
1870
2952
  async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2953
+ var _a, _b, _c;
1871
2954
  const baseHtml = await buildHtmlDocument(doc, config, baseDir);
1872
2955
  const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
1873
2956
  const chromePath = findChromeExecutable();
@@ -1877,6 +2960,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1877
2960
  const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
1878
2961
  const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
1879
2962
  const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
2963
+ const isWin = process.platform === "win32";
1880
2964
  const isolatedFlags = [
1881
2965
  `--user-data-dir=${tmpProfile}`,
1882
2966
  "--no-first-run",
@@ -1887,7 +2971,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1887
2971
  "--disable-default-apps",
1888
2972
  "--disable-extensions",
1889
2973
  "--disable-domain-reliability",
1890
- "--disable-client-side-phishing-detection",
1891
2974
  "--disable-breakpad",
1892
2975
  "--disable-component-extensions-with-background-pages",
1893
2976
  "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
@@ -1896,12 +2979,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1896
2979
  "--mute-audio",
1897
2980
  "--no-service-autorun",
1898
2981
  "--disable-gpu",
1899
- "--no-sandbox",
1900
- "--disable-setuid-sandbox",
1901
- "--allow-file-access-from-files",
1902
- "--disable-web-security",
2982
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
1903
2983
  "--force-color-profile=srgb",
1904
- "--no-pdf-header-footer"
2984
+ "--no-pdf-header-footer",
2985
+ "--window-size=1200,1600"
1905
2986
  ];
1906
2987
  try {
1907
2988
  fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
@@ -1916,7 +2997,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1916
2997
  `--print-to-pdf=${tmpPdf}`,
1917
2998
  fileUrl
1918
2999
  ],
1919
- { timeout: 3e4 }
3000
+ { timeout: 3e4, windowsHide: true }
1920
3001
  );
1921
3002
  if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
1922
3003
  res = spawnSync(
@@ -1927,12 +3008,78 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1927
3008
  `--print-to-pdf=${tmpPdf}`,
1928
3009
  fileUrl
1929
3010
  ],
1930
- { timeout: 3e4 }
3011
+ { timeout: 3e4, windowsHide: true }
1931
3012
  );
1932
3013
  }
1933
3014
  if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
1934
3015
  const pdfBuffer = fs4.readFileSync(tmpPdf);
1935
- 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
+ }
1936
3083
  }
1937
3084
  } catch {
1938
3085
  } finally {
@@ -2187,6 +3334,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2187
3334
  );
2188
3335
  continue;
2189
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
+ }
2190
3360
  runs.push(
2191
3361
  new TextRun({
2192
3362
  text: span.content,
@@ -2201,7 +3371,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2201
3371
  return runs;
2202
3372
  }
2203
3373
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2204
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3374
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2205
3375
  const resolved = resolveDocumentConfig(doc.metadata, config);
2206
3376
  const docElements = [];
2207
3377
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -2212,7 +3382,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2212
3382
  const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2213
3383
  const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2214
3384
  const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2215
- if (resolved.title) {
3385
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2216
3386
  docElements.push(
2217
3387
  new Paragraph({
2218
3388
  children: [
@@ -2628,7 +3798,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2628
3798
  }
2629
3799
  if (node.type === "table" && node.children) {
2630
3800
  const tableRows = [];
2631
- 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;
2632
3802
  const colWidth = Math.floor(9e3 / numCols);
2633
3803
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2634
3804
  const rowNode = node.children[rowIdx];
@@ -2638,7 +3808,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2638
3808
  if (rowNode.children) {
2639
3809
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2640
3810
  const cellNode = rowNode.children[colIdx];
2641
- const align = (_c = node.align) == null ? void 0 : _c[colIdx];
3811
+ const align = (_d = node.align) == null ? void 0 : _d[colIdx];
2642
3812
  let alignment = AlignmentType.LEFT;
2643
3813
  if (align === "center") alignment = AlignmentType.CENTER;
2644
3814
  if (align === "right") alignment = AlignmentType.RIGHT;
@@ -2777,15 +3947,175 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2777
3947
  }
2778
3948
  continue;
2779
3949
  }
2780
- }
2781
- const contentWidthTwip = Math.max(
2782
- 1e3,
2783
- resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2784
- );
2785
- const centerPos = Math.round(contentWidthTwip / 2);
2786
- const rightPos = contentWidthTwip;
2787
- const headerRuns = [];
2788
- if ((_d = resolved.header) == null ? void 0 : _d.left) {
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,
4063
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4064
+ );
4065
+ docElements.push(new Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
4066
+ const sigCells = [];
4067
+ const colWidths = [];
4068
+ if (numItems === 1) {
4069
+ const cardWidth = Math.min(3400, Math.floor(contentWidth * 0.42));
4070
+ const spacerWidth = contentWidth - cardWidth;
4071
+ const cardCell = await buildDocxSignatureCell(sig.items[0], sig, cardWidth, defaultFont, baseDir);
4072
+ if (sig.align === "left") {
4073
+ colWidths.push(cardWidth, spacerWidth);
4074
+ sigCells.push(cardCell, createEmptyDocxCell(spacerWidth));
4075
+ } else if (sig.align === "center") {
4076
+ const sideWidth = Math.floor(spacerWidth / 2);
4077
+ colWidths.push(sideWidth, cardWidth, sideWidth);
4078
+ sigCells.push(createEmptyDocxCell(sideWidth), cardCell, createEmptyDocxCell(sideWidth));
4079
+ } else {
4080
+ colWidths.push(spacerWidth, cardWidth);
4081
+ sigCells.push(createEmptyDocxCell(spacerWidth), cardCell);
4082
+ }
4083
+ } else {
4084
+ const colWidth = Math.floor(contentWidth / numItems);
4085
+ for (let i = 0; i < numItems; i++) {
4086
+ colWidths.push(colWidth);
4087
+ const cell = await buildDocxSignatureCell(sig.items[i], sig, colWidth, defaultFont, baseDir);
4088
+ sigCells.push(cell);
4089
+ }
4090
+ }
4091
+ const sigTable = new Table({
4092
+ width: { size: 100, type: WidthType.PERCENTAGE },
4093
+ columnWidths: colWidths,
4094
+ borders: {
4095
+ top: { style: BorderStyle.NONE, size: 0, color: "auto" },
4096
+ bottom: { style: BorderStyle.NONE, size: 0, color: "auto" },
4097
+ left: { style: BorderStyle.NONE, size: 0, color: "auto" },
4098
+ right: { style: BorderStyle.NONE, size: 0, color: "auto" },
4099
+ insideHorizontal: { style: BorderStyle.NONE, size: 0, color: "auto" },
4100
+ insideVertical: { style: BorderStyle.NONE, size: 0, color: "auto" }
4101
+ },
4102
+ rows: [
4103
+ new TableRow({
4104
+ cantSplit: true,
4105
+ children: sigCells
4106
+ })
4107
+ ]
4108
+ });
4109
+ docElements.push(sigTable);
4110
+ }
4111
+ const contentWidthTwip = Math.max(
4112
+ 1e3,
4113
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4114
+ );
4115
+ const centerPos = Math.round(contentWidthTwip / 2);
4116
+ const rightPos = contentWidthTwip;
4117
+ const headerRuns = [];
4118
+ if ((_e = resolved.header) == null ? void 0 : _e.left) {
2789
4119
  headerRuns.push(
2790
4120
  new TextRun({
2791
4121
  text: resolved.header.left.text,
@@ -2798,7 +4128,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2798
4128
  );
2799
4129
  }
2800
4130
  headerRuns.push(new TextRun({ text: " " }));
2801
- if ((_e = resolved.header) == null ? void 0 : _e.center) {
4131
+ if ((_f = resolved.header) == null ? void 0 : _f.center) {
2802
4132
  headerRuns.push(
2803
4133
  new TextRun({
2804
4134
  text: resolved.header.center.text,
@@ -2811,7 +4141,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2811
4141
  );
2812
4142
  }
2813
4143
  headerRuns.push(new TextRun({ text: " " }));
2814
- if ((_f = resolved.header) == null ? void 0 : _f.right) {
4144
+ if ((_g = resolved.header) == null ? void 0 : _g.right) {
2815
4145
  headerRuns.push(
2816
4146
  new TextRun({
2817
4147
  text: resolved.header.right.text,
@@ -2850,7 +4180,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2850
4180
  ]
2851
4181
  }) : void 0;
2852
4182
  const footerRuns = [];
2853
- if ((_g = resolved.footer) == null ? void 0 : _g.left) {
4183
+ if ((_h = resolved.footer) == null ? void 0 : _h.left) {
2854
4184
  footerRuns.push(
2855
4185
  new TextRun({
2856
4186
  text: resolved.footer.left.text,
@@ -2863,7 +4193,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2863
4193
  );
2864
4194
  }
2865
4195
  footerRuns.push(new TextRun({ text: " " }));
2866
- if ((_h = resolved.footer) == null ? void 0 : _h.center) {
4196
+ if ((_i = resolved.footer) == null ? void 0 : _i.center) {
2867
4197
  footerRuns.push(
2868
4198
  new TextRun({
2869
4199
  text: resolved.footer.center.text,
@@ -2876,7 +4206,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2876
4206
  );
2877
4207
  }
2878
4208
  footerRuns.push(new TextRun({ text: " " }));
2879
- if ((_i = resolved.footer) == null ? void 0 : _i.right) {
4209
+ if ((_j = resolved.footer) == null ? void 0 : _j.right) {
2880
4210
  const rZone = resolved.footer.right;
2881
4211
  const rColor = rZone.color.replace("#", "");
2882
4212
  const rSize = (rZone.fontSize || 9) * 2;
@@ -2961,6 +4291,91 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2961
4291
  ]
2962
4292
  }) : void 0;
2963
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
+ }
2964
4379
  const document = new Document({
2965
4380
  styles: {
2966
4381
  default: {
@@ -2981,32 +4396,383 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2981
4396
  }
2982
4397
  }
2983
4398
  },
2984
- sections: [
2985
- {
2986
- properties: {
2987
- page: {
2988
- size: {
2989
- width: resolved.paperDimensions.widthTwip,
2990
- height: resolved.paperDimensions.heightTwip,
2991
- orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
2992
- },
2993
- margin: {
2994
- top: resolved.margins.topTwip,
2995
- bottom: resolved.margins.bottomTwip,
2996
- left: resolved.margins.leftTwip,
2997
- right: resolved.margins.rightTwip,
2998
- header: 720,
2999
- footer: 720
3000
- }
4399
+ sections: docSections
4400
+ });
4401
+ return await Packer.toBuffer(document);
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
+ }
4643
+ async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
4644
+ const cellParagraphs = [];
4645
+ if (item.title) {
4646
+ cellParagraphs.push(
4647
+ new Paragraph({
4648
+ children: [
4649
+ new TextRun({
4650
+ text: item.title,
4651
+ font: defaultFont,
4652
+ size: 18,
4653
+ // 9pt
4654
+ color: sig.titleColor.replace("#", ""),
4655
+ bold: true
4656
+ })
4657
+ ],
4658
+ spacing: { after: 60 }
4659
+ })
4660
+ );
4661
+ }
4662
+ if (item.image) {
4663
+ const resolvedImg = await resolveImage(item.image, baseDir);
4664
+ if (resolvedImg) {
4665
+ cellParagraphs.push(
4666
+ new Paragraph({
4667
+ children: [
4668
+ new ImageRun({
4669
+ data: resolvedImg.buffer,
4670
+ transformation: {
4671
+ width: 140,
4672
+ height: 60
4673
+ },
4674
+ type: "png"
4675
+ })
4676
+ ],
4677
+ spacing: { before: 40, after: 40 }
4678
+ })
4679
+ );
4680
+ } else {
4681
+ cellParagraphs.push(new Paragraph({ spacing: { before: 240, after: 240 } }));
4682
+ }
4683
+ } else {
4684
+ cellParagraphs.push(new Paragraph({ spacing: { before: 240, after: 240 } }));
4685
+ }
4686
+ if (sig.style === "line") {
4687
+ cellParagraphs.push(
4688
+ new Paragraph({
4689
+ border: {
4690
+ bottom: {
4691
+ style: BorderStyle.SINGLE,
4692
+ size: 6,
4693
+ space: 2,
4694
+ color: sig.borderColor.replace("#", "")
3001
4695
  }
3002
4696
  },
3003
- headers: docHeader ? { default: docHeader } : void 0,
3004
- footers: docFooter ? { default: docFooter } : void 0,
3005
- children: docElements
3006
- }
3007
- ]
4697
+ spacing: { after: 60 }
4698
+ })
4699
+ );
4700
+ }
4701
+ cellParagraphs.push(
4702
+ new Paragraph({
4703
+ children: [
4704
+ new TextRun({
4705
+ text: item.name,
4706
+ font: defaultFont,
4707
+ size: 21,
4708
+ // 10.5pt
4709
+ bold: true,
4710
+ color: sig.nameColor.replace("#", "")
4711
+ })
4712
+ ],
4713
+ spacing: { before: sig.style === "line" ? 40 : 20, after: 20 }
4714
+ })
4715
+ );
4716
+ if (item.role) {
4717
+ cellParagraphs.push(
4718
+ new Paragraph({
4719
+ children: [
4720
+ new TextRun({
4721
+ text: item.role,
4722
+ font: defaultFont,
4723
+ size: 18,
4724
+ // 9pt
4725
+ color: sig.roleColor.replace("#", "")
4726
+ })
4727
+ ],
4728
+ spacing: { after: 20 }
4729
+ })
4730
+ );
4731
+ }
4732
+ if (item.date) {
4733
+ cellParagraphs.push(
4734
+ new Paragraph({
4735
+ children: [
4736
+ new TextRun({
4737
+ text: `Date: ${item.date}`,
4738
+ font: defaultFont,
4739
+ size: 17,
4740
+ // 8.5pt
4741
+ color: sig.roleColor.replace("#", "")
4742
+ })
4743
+ ],
4744
+ spacing: { after: 20 }
4745
+ })
4746
+ );
4747
+ }
4748
+ const isBox = sig.style === "box";
4749
+ const boxBorder = { style: BorderStyle.SINGLE, size: 4, color: sig.borderColor.replace("#", "") };
4750
+ const noneBorder = { style: BorderStyle.NONE, size: 0, color: "auto" };
4751
+ return new TableCell({
4752
+ width: { size: widthDxa, type: WidthType.DXA },
4753
+ shading: isBox ? { fill: "F8FAFC", type: ShadingType.CLEAR } : void 0,
4754
+ margins: isBox ? { top: 140, bottom: 140, left: 160, right: 160 } : { top: 60, bottom: 60, left: 60, right: 60 },
4755
+ borders: {
4756
+ top: isBox ? boxBorder : noneBorder,
4757
+ bottom: isBox ? boxBorder : noneBorder,
4758
+ left: isBox ? boxBorder : noneBorder,
4759
+ right: isBox ? boxBorder : noneBorder
4760
+ },
4761
+ children: cellParagraphs
4762
+ });
4763
+ }
4764
+ function createEmptyDocxCell(widthDxa) {
4765
+ const noneBorder = { style: BorderStyle.NONE, size: 0, color: "auto" };
4766
+ return new TableCell({
4767
+ width: { size: widthDxa, type: WidthType.DXA },
4768
+ borders: {
4769
+ top: noneBorder,
4770
+ bottom: noneBorder,
4771
+ left: noneBorder,
4772
+ right: noneBorder
4773
+ },
4774
+ children: [new Paragraph({})]
3008
4775
  });
3009
- return await Packer.toBuffer(document);
3010
4776
  }
3011
4777
 
3012
4778
  // src/core/engine.ts
@@ -3101,14 +4867,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
3101
4867
  };
3102
4868
  }
3103
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
+
3104
5741
  // src/config/defineConfig.ts
3105
5742
  function defineConfig(config) {
3106
5743
  return config;
3107
5744
  }
3108
5745
 
3109
5746
  // src/version.ts
3110
- import * as fs7 from "fs";
3111
- import * as path7 from "path";
5747
+ import * as fs8 from "fs";
5748
+ import * as path8 from "path";
3112
5749
  import { fileURLToPath } from "url";
3113
5750
  try {
3114
5751
  if (typeof globalThis !== "undefined" && (!globalThis.localStorage || typeof globalThis.localStorage.getItem !== "function")) {
@@ -3130,21 +5767,21 @@ try {
3130
5767
  }
3131
5768
  } catch {
3132
5769
  }
3133
- var FALLBACK_VERSION = "0.2.2";
5770
+ var FALLBACK_VERSION = "0.4.0";
3134
5771
  function readVersionFromPackageJson(fromDir) {
3135
5772
  let currentDir = fromDir;
3136
5773
  for (let i = 0; i < 6; i++) {
3137
5774
  try {
3138
- const pkgJsonPath = path7.join(currentDir, "package.json");
3139
- if (fs7.existsSync(pkgJsonPath)) {
3140
- 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"));
3141
5778
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
3142
5779
  return pkg.version;
3143
5780
  }
3144
5781
  }
3145
5782
  } catch {
3146
5783
  }
3147
- const parentDir = path7.dirname(currentDir);
5784
+ const parentDir = path8.dirname(currentDir);
3148
5785
  if (parentDir === currentDir) break;
3149
5786
  currentDir = parentDir;
3150
5787
  }
@@ -3155,7 +5792,7 @@ function getPackageDir() {
3155
5792
  return __dirname;
3156
5793
  }
3157
5794
  try {
3158
- return path7.dirname(fileURLToPath(import.meta.url));
5795
+ return path8.dirname(fileURLToPath(import.meta.url));
3159
5796
  } catch {
3160
5797
  return process.cwd();
3161
5798
  }
@@ -3166,6 +5803,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3166
5803
  }
3167
5804
  export {
3168
5805
  DEFAULT_CONFIG,
5806
+ KATEX_INLINE_CSS,
3169
5807
  MARKFORGE_VERSION,
3170
5808
  Orientation,
3171
5809
  OutputFormat,
@@ -3194,17 +5832,28 @@ export {
3194
5832
  inlineHtmlImages,
3195
5833
  loadConfig,
3196
5834
  compileMarkdown as markforge,
5835
+ normalizeBackCover,
5836
+ normalizeCoverPage,
3197
5837
  normalizeHeaderFooter,
3198
5838
  normalizeHeaderFooterSlot,
5839
+ normalizeNumberHeadings,
5840
+ normalizeSecurity,
5841
+ normalizeSignatures,
3199
5842
  normalizeWatermark,
3200
5843
  parseInlineSpans,
3201
5844
  parseMarginToTwip2 as parseMarginToTwip,
5845
+ parseMarkdownDocument as parseMarkdown,
3202
5846
  parseMarkdownDocument,
5847
+ renderBackCoverHtml,
5848
+ renderCoverPageHtml,
3203
5849
  renderInlinesToHtml,
5850
+ renderMathToHtml,
3204
5851
  renderMermaidToPng,
5852
+ renderNodesToHtml,
3205
5853
  replaceDocumentTokens,
3206
5854
  resolveDocumentConfig,
3207
5855
  resolveImage,
3208
5856
  slugify,
5857
+ startPreviewServer,
3209
5858
  tokenizeCodeLine
3210
5859
  };