@masumdev/markforge 0.2.5 → 0.3.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.
@@ -9,7 +9,7 @@ try {
9
9
  } catch {}
10
10
  import {
11
11
  MARKFORGE_VERSION
12
- } from "./chunk-NGC2ZAWZ.mjs";
12
+ } from "./chunk-HKB4CSPZ.mjs";
13
13
  import {
14
14
  DEFAULT_CONFIG
15
15
  } from "./chunk-IYGPJIK5.mjs";
@@ -20,33 +20,71 @@ import { useState, useEffect } from "react";
20
20
  import { Box as Box4 } from "ink";
21
21
 
22
22
  // src/ui/Header.tsx
23
+ import * as path from "path";
23
24
  import { Box, Text } from "ink";
24
25
  import { jsx, jsxs } from "react/jsx-runtime";
25
- var Header = ({ inputFile, theme = "corporate" }) => {
26
- const themeLabel = typeof theme === "object" ? "custom (ThemeProps)" : String(theme || "corporate");
26
+ function formatDisplayPath(p) {
27
+ if (!p) return "";
28
+ try {
29
+ const rel = path.relative(process.cwd(), p);
30
+ return rel && !rel.startsWith("..") && rel.length < p.length ? `./${rel}` : p;
31
+ } catch {
32
+ return p;
33
+ }
34
+ }
35
+ var Header = ({
36
+ inputFile,
37
+ theme = "corporate",
38
+ targetFormats = ["docx", "pdf"]
39
+ }) => {
40
+ const themeLabel = typeof theme === "object" ? "Custom (ThemeProps)" : String(theme || "corporate");
27
41
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: /* @__PURE__ */ jsxs(
28
42
  Box,
29
43
  {
30
44
  borderStyle: "round",
31
45
  borderColor: "cyan",
32
46
  paddingX: 2,
33
- paddingY: 0,
47
+ paddingY: 1,
34
48
  flexDirection: "column",
35
49
  children: [
36
- /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
37
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "MARKFORGE DOCUMENT ENGINE" }),
38
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
50
+ /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", alignItems: "center", children: [
51
+ /* @__PURE__ */ jsxs(Box, { children: [
52
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "MARKFORGE" }),
53
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " | Document Publishing Engine" })
54
+ ] }),
55
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "black", backgroundColor: "cyan", bold: true, children: [
56
+ " ",
39
57
  "v",
40
- MARKFORGE_VERSION
41
- ] })
58
+ MARKFORGE_VERSION,
59
+ " "
60
+ ] }) })
42
61
  ] }),
43
- inputFile && /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
44
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Source: " }),
45
- /* @__PURE__ */ jsx(Text, { color: "white", bold: true, children: inputFile })
62
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
63
+ inputFile && /* @__PURE__ */ jsxs(Box, { children: [
64
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Source: " }),
65
+ /* @__PURE__ */ jsx(Text, { color: "white", bold: true, children: formatDisplayPath(inputFile) })
66
+ ] }),
67
+ /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", marginTop: 0, children: [
68
+ /* @__PURE__ */ jsxs(Box, { children: [
69
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Theme: " }),
70
+ /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: themeLabel })
71
+ ] }),
72
+ targetFormats && targetFormats.length > 0 && /* @__PURE__ */ jsxs(Box, { children: [
73
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Targets: " }),
74
+ /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: targetFormats.map((f) => f.toUpperCase()).join(", ") })
75
+ ] })
76
+ ] })
46
77
  ] }),
47
- /* @__PURE__ */ jsxs(Box, { children: [
48
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Theme: " }),
49
- /* @__PURE__ */ jsx(Text, { color: "blue", children: themeLabel })
78
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [
79
+ /* @__PURE__ */ jsxs(Box, { children: [
80
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Author: " }),
81
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Ma'sum" }),
82
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " (@masumrpg)" })
83
+ ] }),
84
+ /* @__PURE__ */ jsxs(Box, { children: [
85
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "GitHub: " }),
86
+ /* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: "https://github.com/masumrpg" })
87
+ ] })
50
88
  ] })
51
89
  ]
52
90
  }
@@ -69,6 +107,7 @@ var LiveProgress = ({ status, isCompiling }) => {
69
107
  };
70
108
 
71
109
  // src/ui/SummaryTable.tsx
110
+ import * as path2 from "path";
72
111
  import { Box as Box3, Text as Text3 } from "ink";
73
112
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
74
113
  function formatBytes(bytes) {
@@ -78,6 +117,15 @@ function formatBytes(bytes) {
78
117
  const i = Math.floor(Math.log(bytes) / Math.log(k));
79
118
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
80
119
  }
120
+ function formatDisplayPath2(p) {
121
+ if (!p) return "";
122
+ try {
123
+ const rel = path2.relative(process.cwd(), p);
124
+ return rel && !rel.startsWith("..") && rel.length < p.length ? `./${rel}` : p;
125
+ } catch {
126
+ return p;
127
+ }
128
+ }
81
129
  var SummaryTable = ({ result }) => {
82
130
  if (!result) return null;
83
131
  const hasErrors = result.errors.length > 0;
@@ -90,33 +138,39 @@ var SummaryTable = ({ result }) => {
90
138
  paddingY: 1,
91
139
  flexDirection: "column",
92
140
  children: [
93
- /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", children: [
94
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: hasErrors ? "red" : "green", children: hasErrors ? "[COMPILATION ERRORS]" : "[COMPILATION SUCCESSFUL]" }),
95
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
96
- result.durationMs,
97
- "ms"
141
+ /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", alignItems: "center", children: [
142
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: hasErrors ? "red" : "green", children: hasErrors ? "[COMPILATION FAILED]" : "[COMPILATION SUCCESSFUL]" }) }),
143
+ /* @__PURE__ */ jsxs3(Box3, { children: [
144
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "Duration: " }),
145
+ /* @__PURE__ */ jsxs3(Text3, { color: "yellow", bold: true, children: [
146
+ result.durationMs,
147
+ "ms"
148
+ ] })
98
149
  ] })
99
150
  ] }),
100
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
151
+ result.files.length > 0 && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
101
152
  /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "white", children: [
102
- "Output Documents (",
153
+ "Generated Documents (",
103
154
  result.files.length,
104
155
  "):"
105
156
  ] }),
106
- result.files.map((f, i) => /* @__PURE__ */ jsxs3(Box3, { marginLeft: 2, justifyContent: "space-between", children: [
107
- /* @__PURE__ */ jsxs3(Box3, { children: [
108
- /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
109
- "[",
110
- f.format.toUpperCase(),
111
- "] "
157
+ result.files.map((f, i) => {
158
+ const formatColor = f.format === "docx" ? "blue" : f.format === "pdf" ? "red" : "yellow";
159
+ const paddedBadge = `[${f.format.toUpperCase()}]`.padEnd(7, " ");
160
+ return /* @__PURE__ */ jsxs3(Box3, { marginTop: 0, flexDirection: "column", marginLeft: 1, children: [
161
+ /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", alignItems: "center", children: [
162
+ /* @__PURE__ */ jsxs3(Box3, { children: [
163
+ /* @__PURE__ */ jsx3(Text3, { color: formatColor, bold: true, children: paddedBadge }),
164
+ /* @__PURE__ */ jsx3(Text3, { color: "white", bold: true, children: f.fileName })
165
+ ] }),
166
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: formatBytes(f.sizeBytes) })
112
167
  ] }),
113
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: f.filePath })
114
- ] }),
115
- /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
116
- " ",
117
- formatBytes(f.sizeBytes)
118
- ] })
119
- ] }, i))
168
+ /* @__PURE__ */ jsx3(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
169
+ "\u2514\u2500 ",
170
+ formatDisplayPath2(f.filePath)
171
+ ] }) })
172
+ ] }, i);
173
+ })
120
174
  ] }),
121
175
  hasErrors && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
122
176
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: "red", children: "Errors:" }),
@@ -124,6 +178,17 @@ var SummaryTable = ({ result }) => {
124
178
  "- ",
125
179
  err
126
180
  ] }) }, i))
181
+ ] }),
182
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, justifyContent: "space-between", alignItems: "center", children: [
183
+ /* @__PURE__ */ jsxs3(Box3, { children: [
184
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "Author: " }),
185
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "Ma'sum" }),
186
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " (@masumrpg)" })
187
+ ] }),
188
+ /* @__PURE__ */ jsxs3(Box3, { children: [
189
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "GitHub: " }),
190
+ /* @__PURE__ */ jsx3(Text3, { color: "blue", underline: true, children: "https://github.com/masumrpg" })
191
+ ] })
127
192
  ] })
128
193
  ]
129
194
  }
@@ -132,7 +197,7 @@ var SummaryTable = ({ result }) => {
132
197
 
133
198
  // src/core/engine.ts
134
199
  import * as fs5 from "fs";
135
- import * as path5 from "path";
200
+ import * as path7 from "path";
136
201
 
137
202
  // src/core/parser.ts
138
203
  import matter from "gray-matter";
@@ -526,7 +591,7 @@ import {
526
591
 
527
592
  // src/core/imageResolver.ts
528
593
  import * as fs from "fs";
529
- import * as path from "path";
594
+ import * as path3 from "path";
530
595
  var memoryImageCache = /* @__PURE__ */ new Map();
531
596
  function getMimeType(filePathOrUrl) {
532
597
  const clean = filePathOrUrl.split("?")[0].toLowerCase();
@@ -583,7 +648,7 @@ async function resolveImage(src, baseDir = process.cwd()) {
583
648
  memoryImageCache.set(cacheKey, resolved2);
584
649
  return resolved2;
585
650
  }
586
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
651
+ const localPath = path3.isAbsolute(src) ? src : path3.resolve(baseDir, src);
587
652
  if (!fs.existsSync(localPath)) {
588
653
  return null;
589
654
  }
@@ -964,19 +1029,19 @@ function highlightCodeToHtml(code, lang = "", theme = "github-dark") {
964
1029
  import { spawnSync as spawnSync2 } from "child_process";
965
1030
  import * as fs4 from "fs";
966
1031
  import * as os2 from "os";
967
- import * as path4 from "path";
1032
+ import * as path6 from "path";
968
1033
  import { pathToFileURL as pathToFileURL2 } from "url";
969
1034
 
970
1035
  // src/core/pdf/pdfBuilder.ts
971
1036
  import * as fs3 from "fs";
972
- import * as path3 from "path";
1037
+ import * as path5 from "path";
973
1038
  import * as os from "os";
974
1039
  import { pathToFileURL } from "url";
975
1040
  import { spawnSync } from "child_process";
976
1041
 
977
1042
  // src/core/html/htmlBuilder.ts
978
1043
  import * as fs2 from "fs";
979
- import * as path2 from "path";
1044
+ import * as path4 from "path";
980
1045
 
981
1046
  // src/core/html/htmlThemes.ts
982
1047
  var THEME_COMPONENTS = `
@@ -1257,6 +1322,67 @@ function normalizeHeaderFooter(raw, meta) {
1257
1322
  dividerColor: raw.dividerColor || "#E2E8F0"
1258
1323
  };
1259
1324
  }
1325
+ function normalizeSignatures(raw, meta = {}) {
1326
+ if (!raw) return void 0;
1327
+ let rawItems = [];
1328
+ let rawConfig = {};
1329
+ if (Array.isArray(raw)) {
1330
+ rawItems = raw;
1331
+ } else if (typeof raw === "object" && Array.isArray(raw.items)) {
1332
+ rawItems = raw.items;
1333
+ rawConfig = raw;
1334
+ }
1335
+ if (rawItems.length === 0) return void 0;
1336
+ const cappedItems = rawItems.slice(0, 4);
1337
+ const items = cappedItems.map((item) => {
1338
+ const tokenCtx = meta;
1339
+ const rawName = typeof item.name === "string" ? item.name : "";
1340
+ const name = replaceDocumentTokens(rawName, tokenCtx).trim();
1341
+ const title = item.title ? replaceDocumentTokens(item.title, tokenCtx).trim() : void 0;
1342
+ const role = item.role ? replaceDocumentTokens(item.role, tokenCtx).trim() : void 0;
1343
+ let dateStr;
1344
+ if (typeof item.date === "string") {
1345
+ dateStr = replaceDocumentTokens(item.date, tokenCtx).trim();
1346
+ } else if (item.date === true) {
1347
+ dateStr = meta.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1348
+ }
1349
+ let signatureHeight = 60;
1350
+ if (typeof item.signatureHeight === "number") {
1351
+ signatureHeight = item.signatureHeight;
1352
+ } else if (typeof item.signatureHeight === "string") {
1353
+ const parsed = parseFloat(item.signatureHeight);
1354
+ if (!isNaN(parsed)) signatureHeight = parsed;
1355
+ }
1356
+ return {
1357
+ title,
1358
+ name: name || "Authorized Signatory",
1359
+ role,
1360
+ date: dateStr,
1361
+ image: item.image,
1362
+ signatureHeight
1363
+ };
1364
+ });
1365
+ const align = rawConfig.align || (items.length === 1 ? "right" : "space-between");
1366
+ const style = rawConfig.style || "line";
1367
+ const borderColor = rawConfig.borderColor || "#CBD5E1";
1368
+ const titleColor = rawConfig.titleColor || "#64748B";
1369
+ const nameColor = rawConfig.nameColor || "#0F172A";
1370
+ const roleColor = rawConfig.roleColor || "#64748B";
1371
+ const spacingBeforeRaw = rawConfig.spacingBefore ?? "2.5rem";
1372
+ const spacingBefore = formatMarginCss(spacingBeforeRaw, "2.5rem");
1373
+ const spacingBeforeTwip = parseMarginToTwip(spacingBeforeRaw, 600);
1374
+ return {
1375
+ items,
1376
+ align,
1377
+ style,
1378
+ borderColor,
1379
+ titleColor,
1380
+ nameColor,
1381
+ roleColor,
1382
+ spacingBefore,
1383
+ spacingBeforeTwip
1384
+ };
1385
+ }
1260
1386
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1261
1387
  const configMeta = userConfig.metadata || {};
1262
1388
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1297,6 +1423,8 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1297
1423
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1298
1424
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1299
1425
  const watermark = normalizeWatermark(rawWatermark);
1426
+ const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1427
+ const signatures = normalizeSignatures(rawSignatures, tokenContext);
1300
1428
  const cssList = [];
1301
1429
  const addCss = (item) => {
1302
1430
  if (!item) return;
@@ -1324,6 +1452,7 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1324
1452
  header,
1325
1453
  footer,
1326
1454
  toc,
1455
+ signatures,
1327
1456
  watermark,
1328
1457
  css: cssList,
1329
1458
  embedImages,
@@ -1387,7 +1516,7 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1387
1516
  const baseThemeCss = generateThemeCss(resolved.theme);
1388
1517
  let customCss = "";
1389
1518
  for (const cssPath of resolved.css) {
1390
- const fullCssPath = path2.isAbsolute(cssPath) ? cssPath : path2.resolve(baseDir, cssPath);
1519
+ const fullCssPath = path4.isAbsolute(cssPath) ? cssPath : path4.resolve(baseDir, cssPath);
1391
1520
  if (fs2.existsSync(fullCssPath)) {
1392
1521
  customCss += `
1393
1522
  /* Custom CSS: ${cssPath} */
@@ -1546,26 +1675,160 @@ ${escapeHtml(node.text || "")}
1546
1675
  watermarkCss = `
1547
1676
  .document-watermark {
1548
1677
  position: fixed;
1549
- top: 50%;
1550
- left: 50%;
1551
- transform: translate(-50%, -50%) rotate(${wm.rotate}deg);
1552
- font-size: ${wm.fontSize}pt;
1553
- font-weight: 900;
1554
- color: ${wm.color};
1555
- opacity: ${wm.opacity};
1678
+ top: 0;
1679
+ left: 0;
1680
+ width: 100vw;
1681
+ height: 100vh;
1556
1682
  pointer-events: none;
1557
1683
  z-index: 0;
1558
1684
  user-select: none;
1559
- text-transform: uppercase;
1560
- letter-spacing: 0.15em;
1561
- white-space: nowrap;
1685
+ -webkit-user-select: none;
1562
1686
  }
1563
1687
  .document-container {
1564
1688
  position: relative;
1565
1689
  z-index: 1;
1566
1690
  }
1691
+ @media print {
1692
+ .document-watermark {
1693
+ position: fixed;
1694
+ top: 0;
1695
+ left: 0;
1696
+ width: 100vw;
1697
+ height: 100vh;
1698
+ -webkit-print-color-adjust: exact;
1699
+ print-color-adjust: exact;
1700
+ }
1701
+ }
1567
1702
  `;
1568
- watermarkHtml = ` <div class="document-watermark">${escapeHtml(wm.text)}</div>
1703
+ watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
1704
+ <script>
1705
+ (function() {
1706
+ try {
1707
+ var canvas = document.createElement('canvas');
1708
+ var dpr = 3;
1709
+ var width = 800;
1710
+ var height = 1100;
1711
+ canvas.width = Math.round(width * dpr);
1712
+ canvas.height = Math.round(height * dpr);
1713
+ var ctx = canvas.getContext('2d');
1714
+ if (ctx) {
1715
+ ctx.scale(dpr, dpr);
1716
+ ctx.translate(width / 2, height / 2);
1717
+ ctx.rotate((${wm.rotate} * Math.PI) / 180);
1718
+ ctx.textAlign = 'center';
1719
+ ctx.textBaseline = 'middle';
1720
+ ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
1721
+ ctx.fillStyle = '${wm.color}';
1722
+ ctx.globalAlpha = ${wm.opacity};
1723
+ try { ctx.letterSpacing = '0.15em'; } catch(e) {}
1724
+ ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
1725
+ var dataUrl = canvas.toDataURL('image/png');
1726
+ var wmEl = document.getElementById('markforge-watermark');
1727
+ if (wmEl) {
1728
+ wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
1729
+ wmEl.style.backgroundRepeat = 'no-repeat';
1730
+ wmEl.style.backgroundPosition = 'center center';
1731
+ wmEl.style.backgroundSize = 'contain';
1732
+ }
1733
+ }
1734
+ } catch(err) {}
1735
+ })();
1736
+ </script>
1737
+ `;
1738
+ }
1739
+ let signaturesHtml = "";
1740
+ let signaturesCss = "";
1741
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
1742
+ const sig = resolved.signatures;
1743
+ const numItems = sig.items.length;
1744
+ let justifyCss = "flex-end";
1745
+ if (sig.align === "left") justifyCss = "flex-start";
1746
+ else if (sig.align === "center") justifyCss = "center";
1747
+ else if (sig.align === "space-between") justifyCss = "space-between";
1748
+ signaturesCss = `
1749
+ .markforge-signatures {
1750
+ margin-top: ${sig.spacingBefore};
1751
+ display: grid;
1752
+ 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))`};
1753
+ gap: 2rem;
1754
+ page-break-inside: avoid;
1755
+ break-inside: avoid;
1756
+ }
1757
+ .markforge-signature-card {
1758
+ ${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
1759
+ ${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
1760
+ display: flex;
1761
+ flex-direction: column;
1762
+ ${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
1763
+ }
1764
+ .markforge-sig-title {
1765
+ font-size: 0.85rem;
1766
+ color: ${sig.titleColor};
1767
+ font-weight: 600;
1768
+ margin-bottom: 6px;
1769
+ }
1770
+ .markforge-sig-space {
1771
+ height: var(--sig-height, 60px);
1772
+ display: flex;
1773
+ align-items: center;
1774
+ justify-content: center;
1775
+ margin-bottom: 6px;
1776
+ }
1777
+ .markforge-sig-space img {
1778
+ max-height: 100%;
1779
+ max-width: 100%;
1780
+ object-fit: contain;
1781
+ }
1782
+ .markforge-sig-line {
1783
+ ${sig.style === "line" ? `border-bottom: 1.5px solid ${sig.borderColor}; margin-bottom: 8px;` : ""}
1784
+ }
1785
+ .markforge-sig-name {
1786
+ font-size: 0.95rem;
1787
+ font-weight: 700;
1788
+ color: ${sig.nameColor};
1789
+ }
1790
+ .markforge-sig-role {
1791
+ font-size: 0.82rem;
1792
+ color: ${sig.roleColor};
1793
+ margin-top: 2px;
1794
+ }
1795
+ .markforge-sig-date {
1796
+ font-size: 0.78rem;
1797
+ color: ${sig.roleColor};
1798
+ margin-top: 2px;
1799
+ }
1800
+ @media print {
1801
+ .markforge-signatures {
1802
+ page-break-inside: avoid;
1803
+ break-inside: avoid;
1804
+ }
1805
+ }
1806
+ `;
1807
+ const itemCards = sig.items.map((item) => {
1808
+ const titleHtml = item.title ? `<div class="markforge-sig-title">${escapeHtml(item.title)}</div>` : "";
1809
+ let signSpaceHtml = "";
1810
+ if (item.image) {
1811
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"><img src="${escapeHtml(item.image)}" alt="Signature" /></div>`;
1812
+ } else {
1813
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"></div>`;
1814
+ }
1815
+ const lineHtml = sig.style === "line" ? `<div class="markforge-sig-line"></div>` : "";
1816
+ const nameHtml = `<div class="markforge-sig-name">${escapeHtml(item.name)}</div>`;
1817
+ const roleHtml = item.role ? `<div class="markforge-sig-role">${escapeHtml(item.role)}</div>` : "";
1818
+ const dateHtml = item.date ? `<div class="markforge-sig-date">Date: ${escapeHtml(item.date)}</div>` : "";
1819
+ return ` <div class="markforge-signature-card">
1820
+ ${titleHtml}
1821
+ ${signSpaceHtml}
1822
+ ${lineHtml}
1823
+ ${nameHtml}
1824
+ ${roleHtml}
1825
+ ${dateHtml}
1826
+ </div>`;
1827
+ }).join("\n");
1828
+ signaturesHtml = `
1829
+ <div class="markforge-signatures">
1830
+ ${itemCards}
1831
+ </div>
1569
1832
  `;
1570
1833
  }
1571
1834
  const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
@@ -1596,11 +1859,12 @@ ${baseThemeCss}
1596
1859
  ${customCss}
1597
1860
  ${inlinedCss}
1598
1861
  ${watermarkCss}
1862
+ ${signaturesCss}
1599
1863
  </style>
1600
1864
  </head>
1601
1865
  <body>
1602
1866
  ${watermarkHtml} <div class="document-container">
1603
- ${bodyHtml} </div>
1867
+ ${bodyHtml}${signaturesHtml} </div>
1604
1868
  ${mermaidScript}
1605
1869
  </body>
1606
1870
  </html>`;
@@ -1784,9 +2048,9 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1784
2048
  if (chromePath) {
1785
2049
  const tmpId = Math.random().toString(36).substring(2, 9);
1786
2050
  const tmpDir = os.tmpdir();
1787
- const tmpHtml = path3.join(tmpDir, `markforge_${tmpId}.html`);
1788
- const tmpPdf = path3.join(tmpDir, `markforge_${tmpId}.pdf`);
1789
- const tmpProfile = path3.join(tmpDir, `markforge_prof_${tmpId}`);
2051
+ const tmpHtml = path5.join(tmpDir, `markforge_${tmpId}.html`);
2052
+ const tmpPdf = path5.join(tmpDir, `markforge_${tmpId}.pdf`);
2053
+ const tmpProfile = path5.join(tmpDir, `markforge_prof_${tmpId}`);
1790
2054
  const isolatedFlags = [
1791
2055
  `--user-data-dir=${tmpProfile}`,
1792
2056
  "--no-first-run",
@@ -1865,8 +2129,8 @@ async function renderMermaidToPng(mermaidCode, _baseDir = process.cwd()) {
1865
2129
  }
1866
2130
  const tmpId = Math.random().toString(36).substring(2, 9);
1867
2131
  const tmpDir = os2.tmpdir();
1868
- const tmpHtml = path4.join(tmpDir, `mermaid_${tmpId}.html`);
1869
- const tmpScreenshot = path4.join(tmpDir, `mermaid_${tmpId}.png`);
2132
+ const tmpHtml = path6.join(tmpDir, `mermaid_${tmpId}.html`);
2133
+ const tmpScreenshot = path6.join(tmpDir, `mermaid_${tmpId}.png`);
1870
2134
  const htmlContent = `<!DOCTYPE html>
1871
2135
  <html>
1872
2136
  <head>
@@ -2665,6 +2929,59 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2665
2929
  continue;
2666
2930
  }
2667
2931
  }
2932
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
2933
+ const sig = resolved.signatures;
2934
+ const numItems = sig.items.length;
2935
+ const contentWidth = Math.max(
2936
+ 1e3,
2937
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2938
+ );
2939
+ docElements.push(new Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
2940
+ const sigCells = [];
2941
+ const colWidths = [];
2942
+ if (numItems === 1) {
2943
+ const cardWidth = Math.min(3400, Math.floor(contentWidth * 0.42));
2944
+ const spacerWidth = contentWidth - cardWidth;
2945
+ const cardCell = await buildDocxSignatureCell(sig.items[0], sig, cardWidth, defaultFont, baseDir);
2946
+ if (sig.align === "left") {
2947
+ colWidths.push(cardWidth, spacerWidth);
2948
+ sigCells.push(cardCell, createEmptyDocxCell(spacerWidth));
2949
+ } else if (sig.align === "center") {
2950
+ const sideWidth = Math.floor(spacerWidth / 2);
2951
+ colWidths.push(sideWidth, cardWidth, sideWidth);
2952
+ sigCells.push(createEmptyDocxCell(sideWidth), cardCell, createEmptyDocxCell(sideWidth));
2953
+ } else {
2954
+ colWidths.push(spacerWidth, cardWidth);
2955
+ sigCells.push(createEmptyDocxCell(spacerWidth), cardCell);
2956
+ }
2957
+ } else {
2958
+ const colWidth = Math.floor(contentWidth / numItems);
2959
+ for (let i = 0; i < numItems; i++) {
2960
+ colWidths.push(colWidth);
2961
+ const cell = await buildDocxSignatureCell(sig.items[i], sig, colWidth, defaultFont, baseDir);
2962
+ sigCells.push(cell);
2963
+ }
2964
+ }
2965
+ const sigTable = new Table({
2966
+ width: { size: 100, type: WidthType.PERCENTAGE },
2967
+ columnWidths: colWidths,
2968
+ borders: {
2969
+ top: { style: BorderStyle.NONE, size: 0, color: "auto" },
2970
+ bottom: { style: BorderStyle.NONE, size: 0, color: "auto" },
2971
+ left: { style: BorderStyle.NONE, size: 0, color: "auto" },
2972
+ right: { style: BorderStyle.NONE, size: 0, color: "auto" },
2973
+ insideHorizontal: { style: BorderStyle.NONE, size: 0, color: "auto" },
2974
+ insideVertical: { style: BorderStyle.NONE, size: 0, color: "auto" }
2975
+ },
2976
+ rows: [
2977
+ new TableRow({
2978
+ cantSplit: true,
2979
+ children: sigCells
2980
+ })
2981
+ ]
2982
+ });
2983
+ docElements.push(sigTable);
2984
+ }
2668
2985
  const contentWidthTwip = Math.max(
2669
2986
  1e3,
2670
2987
  resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
@@ -2895,6 +3212,140 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2895
3212
  });
2896
3213
  return await Packer.toBuffer(document);
2897
3214
  }
3215
+ async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
3216
+ const cellParagraphs = [];
3217
+ if (item.title) {
3218
+ cellParagraphs.push(
3219
+ new Paragraph({
3220
+ children: [
3221
+ new TextRun({
3222
+ text: item.title,
3223
+ font: defaultFont,
3224
+ size: 18,
3225
+ // 9pt
3226
+ color: sig.titleColor.replace("#", ""),
3227
+ bold: true
3228
+ })
3229
+ ],
3230
+ spacing: { after: 60 }
3231
+ })
3232
+ );
3233
+ }
3234
+ if (item.image) {
3235
+ const resolvedImg = await resolveImage(item.image, baseDir);
3236
+ if (resolvedImg) {
3237
+ cellParagraphs.push(
3238
+ new Paragraph({
3239
+ children: [
3240
+ new ImageRun({
3241
+ data: resolvedImg.buffer,
3242
+ transformation: {
3243
+ width: 140,
3244
+ height: 60
3245
+ },
3246
+ type: "png"
3247
+ })
3248
+ ],
3249
+ spacing: { before: 40, after: 40 }
3250
+ })
3251
+ );
3252
+ } else {
3253
+ cellParagraphs.push(new Paragraph({ spacing: { before: 240, after: 240 } }));
3254
+ }
3255
+ } else {
3256
+ cellParagraphs.push(new Paragraph({ spacing: { before: 240, after: 240 } }));
3257
+ }
3258
+ if (sig.style === "line") {
3259
+ cellParagraphs.push(
3260
+ new Paragraph({
3261
+ border: {
3262
+ bottom: {
3263
+ style: BorderStyle.SINGLE,
3264
+ size: 6,
3265
+ space: 2,
3266
+ color: sig.borderColor.replace("#", "")
3267
+ }
3268
+ },
3269
+ spacing: { after: 60 }
3270
+ })
3271
+ );
3272
+ }
3273
+ cellParagraphs.push(
3274
+ new Paragraph({
3275
+ children: [
3276
+ new TextRun({
3277
+ text: item.name,
3278
+ font: defaultFont,
3279
+ size: 21,
3280
+ // 10.5pt
3281
+ bold: true,
3282
+ color: sig.nameColor.replace("#", "")
3283
+ })
3284
+ ],
3285
+ spacing: { before: sig.style === "line" ? 40 : 20, after: 20 }
3286
+ })
3287
+ );
3288
+ if (item.role) {
3289
+ cellParagraphs.push(
3290
+ new Paragraph({
3291
+ children: [
3292
+ new TextRun({
3293
+ text: item.role,
3294
+ font: defaultFont,
3295
+ size: 18,
3296
+ // 9pt
3297
+ color: sig.roleColor.replace("#", "")
3298
+ })
3299
+ ],
3300
+ spacing: { after: 20 }
3301
+ })
3302
+ );
3303
+ }
3304
+ if (item.date) {
3305
+ cellParagraphs.push(
3306
+ new Paragraph({
3307
+ children: [
3308
+ new TextRun({
3309
+ text: `Date: ${item.date}`,
3310
+ font: defaultFont,
3311
+ size: 17,
3312
+ // 8.5pt
3313
+ color: sig.roleColor.replace("#", "")
3314
+ })
3315
+ ],
3316
+ spacing: { after: 20 }
3317
+ })
3318
+ );
3319
+ }
3320
+ const isBox = sig.style === "box";
3321
+ const boxBorder = { style: BorderStyle.SINGLE, size: 4, color: sig.borderColor.replace("#", "") };
3322
+ const noneBorder = { style: BorderStyle.NONE, size: 0, color: "auto" };
3323
+ return new TableCell({
3324
+ width: { size: widthDxa, type: WidthType.DXA },
3325
+ shading: isBox ? { fill: "F8FAFC", type: ShadingType.CLEAR } : void 0,
3326
+ margins: isBox ? { top: 140, bottom: 140, left: 160, right: 160 } : { top: 60, bottom: 60, left: 60, right: 60 },
3327
+ borders: {
3328
+ top: isBox ? boxBorder : noneBorder,
3329
+ bottom: isBox ? boxBorder : noneBorder,
3330
+ left: isBox ? boxBorder : noneBorder,
3331
+ right: isBox ? boxBorder : noneBorder
3332
+ },
3333
+ children: cellParagraphs
3334
+ });
3335
+ }
3336
+ function createEmptyDocxCell(widthDxa) {
3337
+ const noneBorder = { style: BorderStyle.NONE, size: 0, color: "auto" };
3338
+ return new TableCell({
3339
+ width: { size: widthDxa, type: WidthType.DXA },
3340
+ borders: {
3341
+ top: noneBorder,
3342
+ bottom: noneBorder,
3343
+ left: noneBorder,
3344
+ right: noneBorder
3345
+ },
3346
+ children: [new Paragraph({})]
3347
+ });
3348
+ }
2898
3349
 
2899
3350
  // src/core/engine.ts
2900
3351
  async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgress) {
@@ -2907,15 +3358,15 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2907
3358
  if (fs5.existsSync(inputFilePathOrContent)) {
2908
3359
  isFilePath = true;
2909
3360
  rawMarkdown = fs5.readFileSync(inputFilePathOrContent, "utf-8");
2910
- baseDir = path5.dirname(path5.resolve(inputFilePathOrContent));
2911
- inputFileName = path5.basename(inputFilePathOrContent);
3361
+ baseDir = path7.dirname(path7.resolve(inputFilePathOrContent));
3362
+ inputFileName = path7.basename(inputFilePathOrContent);
2912
3363
  } else {
2913
3364
  rawMarkdown = inputFilePathOrContent;
2914
3365
  }
2915
3366
  onProgress == null ? void 0 : onProgress(`Parsing markdown AST: ${inputFileName}...`);
2916
3367
  const parsedDoc = parseMarkdownDocument(rawMarkdown);
2917
3368
  const baseName = inputFileName.replace(/\.(md|mdx|markdown)$/i, "");
2918
- const outputDir = config.outputDir ? path5.isAbsolute(config.outputDir) ? config.outputDir : path5.resolve(process.cwd(), config.outputDir) : baseDir;
3369
+ const outputDir = config.outputDir ? path7.isAbsolute(config.outputDir) ? config.outputDir : path7.resolve(process.cwd(), config.outputDir) : baseDir;
2919
3370
  if (!fs5.existsSync(outputDir)) {
2920
3371
  fs5.mkdirSync(outputDir, { recursive: true });
2921
3372
  }
@@ -2927,7 +3378,7 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2927
3378
  if (fmt === "docx") {
2928
3379
  onProgress == null ? void 0 : onProgress(`Generating DOCX document: ${baseName}.docx...`);
2929
3380
  const docxBuffer = await buildDocxDocument(parsedDoc, config, baseDir);
2930
- const docxPath = path5.join(outputDir, `${baseName}.docx`);
3381
+ const docxPath = path7.join(outputDir, `${baseName}.docx`);
2931
3382
  fs5.writeFileSync(docxPath, docxBuffer);
2932
3383
  generatedFiles.push({
2933
3384
  format: "docx",
@@ -2938,7 +3389,7 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2938
3389
  } else if (fmt === "html") {
2939
3390
  onProgress == null ? void 0 : onProgress(`Generating HTML document: ${baseName}.html...`);
2940
3391
  const htmlString = await buildHtmlDocument(parsedDoc, config, baseDir);
2941
- const htmlPath = path5.join(outputDir, `${baseName}.html`);
3392
+ const htmlPath = path7.join(outputDir, `${baseName}.html`);
2942
3393
  fs5.writeFileSync(htmlPath, htmlString, "utf-8");
2943
3394
  generatedFiles.push({
2944
3395
  format: "html",
@@ -2949,7 +3400,7 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2949
3400
  } else if (fmt === "pdf") {
2950
3401
  onProgress == null ? void 0 : onProgress(`Generating PDF document: ${baseName}.pdf...`);
2951
3402
  const pdfBuffer = await buildPdfDocument(parsedDoc, config, baseDir);
2952
- const pdfPath = path5.join(outputDir, `${baseName}.pdf`);
3403
+ const pdfPath = path7.join(outputDir, `${baseName}.pdf`);
2953
3404
  fs5.writeFileSync(pdfPath, pdfBuffer);
2954
3405
  generatedFiles.push({
2955
3406
  format: "pdf",
@@ -3002,8 +3453,16 @@ var App = ({ inputFile, config, onComplete }) => {
3002
3453
  }
3003
3454
  run();
3004
3455
  }, [inputFile, config, onComplete]);
3456
+ const targetFormats = Array.isArray(config.to) ? config.to : config.to ? [String(config.to)] : ["docx", "pdf"];
3005
3457
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3006
- /* @__PURE__ */ jsx4(Header, { inputFile, theme: config.theme }),
3458
+ /* @__PURE__ */ jsx4(
3459
+ Header,
3460
+ {
3461
+ inputFile,
3462
+ theme: config.theme,
3463
+ targetFormats
3464
+ }
3465
+ ),
3007
3466
  /* @__PURE__ */ jsx4(LiveProgress, { status, isCompiling }),
3008
3467
  /* @__PURE__ */ jsx4(SummaryTable, { result })
3009
3468
  ] });