@masumdev/markforge 0.2.4 → 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,32 +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 = "default" }) => {
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");
26
41
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: /* @__PURE__ */ jsxs(
27
42
  Box,
28
43
  {
29
44
  borderStyle: "round",
30
45
  borderColor: "cyan",
31
46
  paddingX: 2,
32
- paddingY: 0,
47
+ paddingY: 1,
33
48
  flexDirection: "column",
34
49
  children: [
35
- /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
36
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "MARKFORGE DOCUMENT ENGINE" }),
37
- /* @__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
+ " ",
38
57
  "v",
39
- MARKFORGE_VERSION
40
- ] })
58
+ MARKFORGE_VERSION,
59
+ " "
60
+ ] }) })
41
61
  ] }),
42
- inputFile && /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
43
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Source: " }),
44
- /* @__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
+ ] })
45
77
  ] }),
46
- /* @__PURE__ */ jsxs(Box, { children: [
47
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Theme: " }),
48
- /* @__PURE__ */ jsx(Text, { color: "blue", children: theme })
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
+ ] })
49
88
  ] })
50
89
  ]
51
90
  }
@@ -68,6 +107,7 @@ var LiveProgress = ({ status, isCompiling }) => {
68
107
  };
69
108
 
70
109
  // src/ui/SummaryTable.tsx
110
+ import * as path2 from "path";
71
111
  import { Box as Box3, Text as Text3 } from "ink";
72
112
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
73
113
  function formatBytes(bytes) {
@@ -77,6 +117,15 @@ function formatBytes(bytes) {
77
117
  const i = Math.floor(Math.log(bytes) / Math.log(k));
78
118
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
79
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
+ }
80
129
  var SummaryTable = ({ result }) => {
81
130
  if (!result) return null;
82
131
  const hasErrors = result.errors.length > 0;
@@ -89,33 +138,39 @@ var SummaryTable = ({ result }) => {
89
138
  paddingY: 1,
90
139
  flexDirection: "column",
91
140
  children: [
92
- /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", children: [
93
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: hasErrors ? "red" : "green", children: hasErrors ? "[COMPILATION ERRORS]" : "[COMPILATION SUCCESSFUL]" }),
94
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
95
- result.durationMs,
96
- "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
+ ] })
97
149
  ] })
98
150
  ] }),
99
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
151
+ result.files.length > 0 && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
100
152
  /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "white", children: [
101
- "Output Documents (",
153
+ "Generated Documents (",
102
154
  result.files.length,
103
155
  "):"
104
156
  ] }),
105
- result.files.map((f, i) => /* @__PURE__ */ jsxs3(Box3, { marginLeft: 2, justifyContent: "space-between", children: [
106
- /* @__PURE__ */ jsxs3(Box3, { children: [
107
- /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
108
- "[",
109
- f.format.toUpperCase(),
110
- "] "
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) })
111
167
  ] }),
112
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: f.filePath })
113
- ] }),
114
- /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
115
- " ",
116
- formatBytes(f.sizeBytes)
117
- ] })
118
- ] }, 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
+ })
119
174
  ] }),
120
175
  hasErrors && /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
121
176
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: "red", children: "Errors:" }),
@@ -123,6 +178,17 @@ var SummaryTable = ({ result }) => {
123
178
  "- ",
124
179
  err
125
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
+ ] })
126
192
  ] })
127
193
  ]
128
194
  }
@@ -131,7 +197,7 @@ var SummaryTable = ({ result }) => {
131
197
 
132
198
  // src/core/engine.ts
133
199
  import * as fs5 from "fs";
134
- import * as path5 from "path";
200
+ import * as path7 from "path";
135
201
 
136
202
  // src/core/parser.ts
137
203
  import matter from "gray-matter";
@@ -525,7 +591,7 @@ import {
525
591
 
526
592
  // src/core/imageResolver.ts
527
593
  import * as fs from "fs";
528
- import * as path from "path";
594
+ import * as path3 from "path";
529
595
  var memoryImageCache = /* @__PURE__ */ new Map();
530
596
  function getMimeType(filePathOrUrl) {
531
597
  const clean = filePathOrUrl.split("?")[0].toLowerCase();
@@ -582,7 +648,7 @@ async function resolveImage(src, baseDir = process.cwd()) {
582
648
  memoryImageCache.set(cacheKey, resolved2);
583
649
  return resolved2;
584
650
  }
585
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
651
+ const localPath = path3.isAbsolute(src) ? src : path3.resolve(baseDir, src);
586
652
  if (!fs.existsSync(localPath)) {
587
653
  return null;
588
654
  }
@@ -649,6 +715,51 @@ var SYNTAX_COLORS_LIGHT = {
649
715
  plain: "24292E"
650
716
  // Near-black — identifiers
651
717
  };
718
+ var SYNTAX_THEMES = {
719
+ "github-dark": SYNTAX_COLORS,
720
+ "dark": SYNTAX_COLORS,
721
+ "github-light": SYNTAX_COLORS_LIGHT,
722
+ "light": SYNTAX_COLORS_LIGHT,
723
+ "dracula": {
724
+ keyword: "FF79C6",
725
+ string: "F1FA8C",
726
+ comment: "6272A4",
727
+ number: "BD93F9",
728
+ boolean: "BD93F9",
729
+ function: "50FA7B",
730
+ type: "8BE9FD",
731
+ operator: "FF79C6",
732
+ punctuation: "F8F8F2",
733
+ plain: "F8F8F2"
734
+ },
735
+ "monokai": {
736
+ keyword: "F92672",
737
+ string: "E6DB74",
738
+ comment: "75715E",
739
+ number: "AE81FF",
740
+ boolean: "AE81FF",
741
+ function: "A6E22E",
742
+ type: "66D9EF",
743
+ operator: "F92672",
744
+ punctuation: "F8F8F2",
745
+ plain: "F8F8F2"
746
+ },
747
+ "nord": {
748
+ keyword: "81A1C1",
749
+ string: "A3BE8C",
750
+ comment: "616E88",
751
+ number: "B48EAD",
752
+ boolean: "81A1C1",
753
+ function: "88C0D0",
754
+ type: "8FBCBB",
755
+ operator: "81A1C1",
756
+ punctuation: "ECEFF4",
757
+ plain: "D8DEE9"
758
+ }
759
+ };
760
+ function getSyntaxPalette(themeName = "github-dark") {
761
+ return SYNTAX_THEMES[themeName.toLowerCase()] || SYNTAX_THEMES["github-dark"];
762
+ }
652
763
  var JS_KEYWORDS = /* @__PURE__ */ new Set([
653
764
  "import",
654
765
  "from",
@@ -792,9 +903,9 @@ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
792
903
  "foreign",
793
904
  "references"
794
905
  ]);
795
- function tokenizeCodeLine(line, lang = "", theme = "dark") {
906
+ function tokenizeCodeLine(line, lang = "", theme = "github-dark") {
796
907
  var _a;
797
- const COLORS = theme === "light" ? SYNTAX_COLORS_LIGHT : SYNTAX_COLORS;
908
+ const COLORS = getSyntaxPalette(theme);
798
909
  if (!line) {
799
910
  return [{ text: " ", type: "plain", colorHex: COLORS.plain }];
800
911
  }
@@ -877,6 +988,10 @@ function tokenizeCodeLine(line, lang = "", theme = "dark") {
877
988
  tokens.push({ text: word, type: "keyword", colorHex: COLORS.keyword, bold: true });
878
989
  continue;
879
990
  }
991
+ if (/^[A-Z][a-zA-Z0-9_$]*$/.test(word)) {
992
+ tokens.push({ text: word, type: "type", colorHex: COLORS.type });
993
+ continue;
994
+ }
880
995
  let nextNonWs = pos;
881
996
  while (nextNonWs < line.length && /\s/.test(line[nextNonWs])) {
882
997
  nextNonWs++;
@@ -885,10 +1000,6 @@ function tokenizeCodeLine(line, lang = "", theme = "dark") {
885
1000
  tokens.push({ text: word, type: "function", colorHex: COLORS.function });
886
1001
  continue;
887
1002
  }
888
- if (/^[A-Z][a-zA-Z0-9_$]*$/.test(word)) {
889
- tokens.push({ text: word, type: "type", colorHex: COLORS.type });
890
- continue;
891
- }
892
1003
  tokens.push({ text: word, type: "plain", colorHex: COLORS.plain });
893
1004
  continue;
894
1005
  }
@@ -901,10 +1012,10 @@ function tokenizeCodeLine(line, lang = "", theme = "dark") {
901
1012
  }
902
1013
  return tokens;
903
1014
  }
904
- function highlightCodeToHtml(code, lang = "") {
1015
+ function highlightCodeToHtml(code, lang = "", theme = "github-dark") {
905
1016
  const lines = (code || "").split("\n");
906
1017
  const htmlLines = lines.map((line) => {
907
- const tokens = tokenizeCodeLine(line, lang);
1018
+ const tokens = tokenizeCodeLine(line, lang, theme);
908
1019
  return tokens.map((t) => {
909
1020
  const escaped = t.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
910
1021
  if (t.type === "plain") return escaped;
@@ -918,19 +1029,19 @@ function highlightCodeToHtml(code, lang = "") {
918
1029
  import { spawnSync as spawnSync2 } from "child_process";
919
1030
  import * as fs4 from "fs";
920
1031
  import * as os2 from "os";
921
- import * as path4 from "path";
1032
+ import * as path6 from "path";
922
1033
  import { pathToFileURL as pathToFileURL2 } from "url";
923
1034
 
924
1035
  // src/core/pdf/pdfBuilder.ts
925
1036
  import * as fs3 from "fs";
926
- import * as path3 from "path";
1037
+ import * as path5 from "path";
927
1038
  import * as os from "os";
928
1039
  import { pathToFileURL } from "url";
929
1040
  import { spawnSync } from "child_process";
930
1041
 
931
1042
  // src/core/html/htmlBuilder.ts
932
1043
  import * as fs2 from "fs";
933
- import * as path2 from "path";
1044
+ import * as path4 from "path";
934
1045
 
935
1046
  // src/core/html/htmlThemes.ts
936
1047
  var THEME_COMPONENTS = `
@@ -1013,7 +1124,7 @@ hr { border: none; border-top: 1px solid var(--mf-border); margin: 2rem 0; }
1013
1124
  pre { overflow: visible; white-space: pre-wrap; word-break: break-all; }
1014
1125
  }
1015
1126
  `;
1016
- var THEME_DEFAULT = `
1127
+ var THEME_CORPORATE = `
1017
1128
  :root {
1018
1129
  --mf-bg: #ffffff;
1019
1130
  --mf-text: #0f172a;
@@ -1025,49 +1136,330 @@ var THEME_DEFAULT = `
1025
1136
  --mf-card-bg: #f8fafc;
1026
1137
  --mf-code-bg: #0f172a;
1027
1138
  --mf-code-text: #f8fafc;
1028
- --mf-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
1139
+ --mf-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1029
1140
  --mf-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1030
1141
  }
1031
1142
  body { background-color: var(--mf-bg); color: var(--mf-text); font-family: var(--mf-font-family); font-size: 15px; line-height: 1.65; margin: 0; padding: 2.5rem; }
1032
- .document-container { max-width: 860px; margin: 0 auto; }
1143
+ .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1033
1144
  h1, h2, h3, h4, h5, h6 { color: var(--mf-text); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1034
- h1 { font-size: 2.2rem; border-bottom: 2px solid #33CDCF; padding-bottom: 0.5rem; }
1035
- h2 { font-size: 1.6rem; color: #009DA0; border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
1145
+ h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1146
+ h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
1036
1147
  h3 { font-size: 1.3rem; }
1037
1148
  h4 { font-size: 1.1rem; }
1038
1149
  p { margin: 0.8rem 0; }
1039
1150
  `;
1040
- var THEME_ACADEMIC = `
1151
+ var THEMES = {
1152
+ corporate: THEME_CORPORATE,
1153
+ default: THEME_CORPORATE
1154
+ };
1155
+ function generateThemeCss(theme) {
1156
+ if (!theme || theme === "corporate" || theme === "default" || theme === "corporate" /* CORPORATE */) {
1157
+ return THEME_CORPORATE;
1158
+ }
1159
+ if (typeof theme === "object") {
1160
+ const bg = theme.backgroundColor || "#ffffff";
1161
+ const text = theme.textColor || "#0f172a";
1162
+ const textMuted = theme.textMuted || "#64748b";
1163
+ const primary = theme.primaryColor || "#33CDCF";
1164
+ const primaryDark = theme.primaryDark || primary;
1165
+ const primaryLight = theme.primaryLight || "#ECFDFD";
1166
+ const border = theme.borderColor || "#e2e8f0";
1167
+ const cardBg = theme.cardBackground || "#f8fafc";
1168
+ const codeBg = theme.codeBackground || "#0f172a";
1169
+ const codeText = theme.codeText || "#f8fafc";
1170
+ const font = theme.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
1171
+ const fontMono = theme.fontMono || "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
1172
+ return `
1041
1173
  :root {
1042
- --mf-bg: #ffffff;
1043
- --mf-text: #1a1a1a;
1044
- --mf-text-muted: #555;
1045
- --mf-primary: #33CDCF;
1046
- --mf-primary-dark: #009DA0;
1047
- --mf-primary-light: #ECFDFD;
1048
- --mf-border: #ccc;
1049
- --mf-card-bg: #f9f9f9;
1050
- --mf-code-bg: #1e1e1e;
1051
- --mf-code-text: #d4d4d4;
1052
- --mf-font-family: "Merriweather", "Georgia", "Times New Roman", serif;
1053
- --mf-font-mono: "Courier New", Courier, monospace;
1174
+ --mf-bg: ${bg};
1175
+ --mf-text: ${text};
1176
+ --mf-text-muted: ${textMuted};
1177
+ --mf-primary: ${primary};
1178
+ --mf-primary-dark: ${primaryDark};
1179
+ --mf-primary-light: ${primaryLight};
1180
+ --mf-border: ${border};
1181
+ --mf-card-bg: ${cardBg};
1182
+ --mf-code-bg: ${codeBg};
1183
+ --mf-code-text: ${codeText};
1184
+ --mf-font-family: ${font};
1185
+ --mf-font-mono: ${fontMono};
1054
1186
  }
1055
- body { font-family: var(--mf-font-family); font-size: 16px; line-height: 1.8; padding: 3rem; color: var(--mf-text); }
1056
- .document-container { max-width: 780px; margin: 0 auto; text-align: justify; }
1057
- h1, h2, h3 { font-family: "Times New Roman", Times, serif; font-weight: bold; text-align: left; }
1058
- h1 { font-size: 2rem; border-bottom: 1px solid #000; padding-bottom: 0.3rem; }
1059
- h2 { font-size: 1.4rem; border-bottom: 1px solid #ccc; padding-bottom: 0.2rem; }
1060
- h3 { font-size: 1.2rem; }
1061
- p { margin: 0.9rem 0; }
1187
+ body { background-color: var(--mf-bg); color: var(--mf-text); font-family: var(--mf-font-family); font-size: 15px; line-height: 1.65; margin: 0; padding: 2.5rem; }
1188
+ .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1189
+ h1, h2, h3, h4, h5, h6 { color: var(--mf-text); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1190
+ h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1191
+ h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid var(--mf-border); padding-bottom: 0.4rem; }
1192
+ h3 { font-size: 1.3rem; }
1193
+ h4 { font-size: 1.1rem; }
1194
+ p { margin: 0.8rem 0; }
1195
+ ${theme.customCss || ""}
1062
1196
  `;
1063
- var THEMES = {
1064
- default: THEME_DEFAULT,
1065
- academic: THEME_ACADEMIC,
1066
- github: THEME_DEFAULT,
1067
- corporate: THEME_DEFAULT,
1068
- minimal: THEME_DEFAULT,
1069
- dracula: THEME_DEFAULT
1197
+ }
1198
+ if (typeof theme === "string" && THEMES[theme]) {
1199
+ return THEMES[theme];
1200
+ }
1201
+ return THEME_CORPORATE;
1202
+ }
1203
+
1204
+ // src/config/resolveConfig.ts
1205
+ var PAPER_DIMENSIONS_TWIP = {
1206
+ A4: { width: 11906, height: 16838 },
1207
+ // 210mm x 297mm
1208
+ Letter: { width: 12240, height: 15840 },
1209
+ // 8.5in x 11in
1210
+ Legal: { width: 12240, height: 20160 },
1211
+ // 8.5in x 14in
1212
+ A3: { width: 16838, height: 23811 },
1213
+ // 297mm x 420mm
1214
+ A5: { width: 8390, height: 11906 }
1215
+ // 148mm x 210mm
1070
1216
  };
1217
+ function parseMarginToTwip(margin, defaultTwip = 1440) {
1218
+ if (typeof margin === "number") return margin;
1219
+ if (!margin) return defaultTwip;
1220
+ const str = margin.trim().toLowerCase();
1221
+ if (str.endsWith("cm")) {
1222
+ const cm = parseFloat(str);
1223
+ return isNaN(cm) ? defaultTwip : Math.round(cm * 566.929);
1224
+ }
1225
+ if (str.endsWith("mm")) {
1226
+ const mm = parseFloat(str);
1227
+ return isNaN(mm) ? defaultTwip : Math.round(mm * 56.6929);
1228
+ }
1229
+ if (str.endsWith("in") || str.endsWith("inch")) {
1230
+ const inch = parseFloat(str);
1231
+ return isNaN(inch) ? defaultTwip : Math.round(inch * 1440);
1232
+ }
1233
+ if (str.endsWith("pt")) {
1234
+ const pt = parseFloat(str);
1235
+ return isNaN(pt) ? defaultTwip : Math.round(pt * 20);
1236
+ }
1237
+ const val = parseFloat(str);
1238
+ return isNaN(val) ? defaultTwip : Math.round(val);
1239
+ }
1240
+ function formatMarginCss(margin, defaultCss = "2.5cm") {
1241
+ if (margin === void 0 || margin === null) return defaultCss;
1242
+ if (typeof margin === "number") return `${margin}pt`;
1243
+ const str = margin.trim();
1244
+ if (!str) return defaultCss;
1245
+ if (/^[0-9.]+$/.test(str)) return `${str}pt`;
1246
+ return str;
1247
+ }
1248
+ function replaceDocumentTokens(template = "", meta) {
1249
+ 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 || "");
1250
+ }
1251
+ function normalizeWatermark(rawWatermark) {
1252
+ if (!rawWatermark) {
1253
+ return void 0;
1254
+ }
1255
+ if (typeof rawWatermark === "string") {
1256
+ const text = rawWatermark.trim();
1257
+ if (!text) return void 0;
1258
+ return {
1259
+ text,
1260
+ color: "#94a3b8",
1261
+ opacity: 0.08,
1262
+ fontSize: 54,
1263
+ rotate: -45,
1264
+ position: "diagonal"
1265
+ };
1266
+ }
1267
+ if (typeof rawWatermark === "object") {
1268
+ if (!rawWatermark.text || !rawWatermark.text.trim()) return void 0;
1269
+ return {
1270
+ text: rawWatermark.text.trim(),
1271
+ color: rawWatermark.color || "#94a3b8",
1272
+ opacity: typeof rawWatermark.opacity === "number" ? rawWatermark.opacity : 0.08,
1273
+ fontSize: rawWatermark.fontSize || 54,
1274
+ rotate: typeof rawWatermark.rotate === "number" ? rawWatermark.rotate : -45,
1275
+ position: rawWatermark.position || "diagonal"
1276
+ };
1277
+ }
1278
+ return void 0;
1279
+ }
1280
+ function normalizeHeaderFooterSlot(rawSlot, parent, meta) {
1281
+ if (!rawSlot) return void 0;
1282
+ if (typeof rawSlot === "string") {
1283
+ const text = replaceDocumentTokens(rawSlot, meta).trim();
1284
+ if (!text) return void 0;
1285
+ return {
1286
+ text,
1287
+ color: (parent == null ? void 0 : parent.color) || "#94A3B8",
1288
+ fontSize: (parent == null ? void 0 : parent.size) || 9,
1289
+ fontFamily: (parent == null ? void 0 : parent.font) || "Segoe UI",
1290
+ bold: false,
1291
+ italic: false
1292
+ };
1293
+ }
1294
+ if (typeof rawSlot === "object") {
1295
+ const text = replaceDocumentTokens(rawSlot.text || "", meta).trim();
1296
+ if (!text) return void 0;
1297
+ return {
1298
+ text,
1299
+ color: rawSlot.color || (parent == null ? void 0 : parent.color) || "#94A3B8",
1300
+ fontSize: rawSlot.fontSize || (parent == null ? void 0 : parent.size) || 9,
1301
+ fontFamily: rawSlot.fontFamily || (parent == null ? void 0 : parent.font) || "Segoe UI",
1302
+ bold: Boolean(rawSlot.bold),
1303
+ italic: Boolean(rawSlot.italic)
1304
+ };
1305
+ }
1306
+ return void 0;
1307
+ }
1308
+ function normalizeHeaderFooter(raw, meta) {
1309
+ if (!raw) return void 0;
1310
+ const left = normalizeHeaderFooterSlot(raw.left, raw, meta);
1311
+ const center = normalizeHeaderFooterSlot(raw.center, raw, meta);
1312
+ const right = normalizeHeaderFooterSlot(raw.right, raw, meta);
1313
+ if (!left && !center && !right) return void 0;
1314
+ return {
1315
+ left,
1316
+ center,
1317
+ right,
1318
+ font: raw.font || "Segoe UI",
1319
+ size: raw.size || 9,
1320
+ color: raw.color || "#94A3B8",
1321
+ divider: Boolean(raw.divider),
1322
+ dividerColor: raw.dividerColor || "#E2E8F0"
1323
+ };
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
+ }
1386
+ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1387
+ const configMeta = userConfig.metadata || {};
1388
+ const mergedMeta = { ...configMeta, ...frontmatter };
1389
+ const title = mergedMeta.title || "MarkForge Document";
1390
+ const subtitle = mergedMeta.subtitle || void 0;
1391
+ const author = Array.isArray(mergedMeta.author) ? mergedMeta.author.join(", ") : mergedMeta.author || void 0;
1392
+ const date = mergedMeta.date || void 0;
1393
+ const version = mergedMeta.version || void 0;
1394
+ const company = mergedMeta.company || void 0;
1395
+ const lang = mergedMeta.lang || "en";
1396
+ const tokenContext = { title, subtitle, author, version, date, company };
1397
+ const theme = mergedMeta.theme || userConfig.theme || DEFAULT_CONFIG.theme;
1398
+ const orientation = mergedMeta.orientation || userConfig.orientation || DEFAULT_CONFIG.orientation;
1399
+ const paperSize = mergedMeta.paperSize || userConfig.paperSize || DEFAULT_CONFIG.paperSize;
1400
+ const baseDim = PAPER_DIMENSIONS_TWIP[paperSize] || PAPER_DIMENSIONS_TWIP.A4;
1401
+ const paperDimensions = orientation === "landscape" ? { widthTwip: baseDim.height, heightTwip: baseDim.width } : { widthTwip: baseDim.width, heightTwip: baseDim.height };
1402
+ const fmMargin = mergedMeta.margins || {};
1403
+ const cfgMargin = userConfig.margins || {};
1404
+ const defMargin = DEFAULT_CONFIG.margins || {};
1405
+ const topRaw = fmMargin.top ?? cfgMargin.top ?? defMargin.top ?? "2.5cm";
1406
+ const bottomRaw = fmMargin.bottom ?? cfgMargin.bottom ?? defMargin.bottom ?? "2.5cm";
1407
+ const leftRaw = fmMargin.left ?? cfgMargin.left ?? defMargin.left ?? "2.5cm";
1408
+ const rightRaw = fmMargin.right ?? cfgMargin.right ?? defMargin.right ?? "2.5cm";
1409
+ const margins = {
1410
+ top: formatMarginCss(topRaw),
1411
+ bottom: formatMarginCss(bottomRaw),
1412
+ left: formatMarginCss(leftRaw),
1413
+ right: formatMarginCss(rightRaw),
1414
+ topTwip: parseMarginToTwip(topRaw),
1415
+ bottomTwip: parseMarginToTwip(bottomRaw),
1416
+ leftTwip: parseMarginToTwip(leftRaw),
1417
+ rightTwip: parseMarginToTwip(rightRaw)
1418
+ };
1419
+ const rawHeader = mergedMeta.header || userConfig.header || DEFAULT_CONFIG.header;
1420
+ const rawFooter = mergedMeta.footer || userConfig.footer || DEFAULT_CONFIG.footer;
1421
+ const header = normalizeHeaderFooter(rawHeader, tokenContext);
1422
+ const footer = normalizeHeaderFooter(rawFooter, tokenContext);
1423
+ const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1424
+ const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1425
+ const watermark = normalizeWatermark(rawWatermark);
1426
+ const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1427
+ const signatures = normalizeSignatures(rawSignatures, tokenContext);
1428
+ const cssList = [];
1429
+ const addCss = (item) => {
1430
+ if (!item) return;
1431
+ if (Array.isArray(item)) cssList.push(...item);
1432
+ else cssList.push(item);
1433
+ };
1434
+ addCss(userConfig.css);
1435
+ addCss(mergedMeta.css);
1436
+ const embedImages = typeof userConfig.embedImages === "boolean" ? userConfig.embedImages : DEFAULT_CONFIG.embedImages;
1437
+ const bundleHtml = typeof userConfig.bundleHtml === "boolean" ? userConfig.bundleHtml : DEFAULT_CONFIG.bundleHtml;
1438
+ const syntaxTheme = userConfig.syntaxTheme || DEFAULT_CONFIG.syntaxTheme || "github-dark";
1439
+ return {
1440
+ title,
1441
+ subtitle,
1442
+ author,
1443
+ date,
1444
+ version,
1445
+ company,
1446
+ lang,
1447
+ theme,
1448
+ orientation,
1449
+ paperSize,
1450
+ paperDimensions,
1451
+ margins,
1452
+ header,
1453
+ footer,
1454
+ toc,
1455
+ signatures,
1456
+ watermark,
1457
+ css: cssList,
1458
+ embedImages,
1459
+ bundleHtml,
1460
+ syntaxTheme
1461
+ };
1462
+ }
1071
1463
 
1072
1464
  // src/core/html/htmlBuilder.ts
1073
1465
  function escapeHtml(str) {
@@ -1120,43 +1512,41 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1120
1512
  return result;
1121
1513
  }
1122
1514
  async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1123
- var _a;
1124
- const metadata = { ...config.metadata, ...doc.metadata };
1125
- const themeName = metadata.theme || config.theme || "default";
1126
- const baseThemeCss = THEMES[themeName] || THEMES.default;
1515
+ const resolved = resolveDocumentConfig(doc.metadata, config);
1516
+ const baseThemeCss = generateThemeCss(resolved.theme);
1127
1517
  let customCss = "";
1128
- if (config.css) {
1129
- const cssList = Array.isArray(config.css) ? config.css : [config.css];
1130
- for (const cssPath of cssList) {
1131
- const fullCssPath = path2.isAbsolute(cssPath) ? cssPath : path2.resolve(baseDir, cssPath);
1132
- if (fs2.existsSync(fullCssPath)) {
1133
- customCss += `
1518
+ for (const cssPath of resolved.css) {
1519
+ const fullCssPath = path4.isAbsolute(cssPath) ? cssPath : path4.resolve(baseDir, cssPath);
1520
+ if (fs2.existsSync(fullCssPath)) {
1521
+ customCss += `
1134
1522
  /* Custom CSS: ${cssPath} */
1135
1523
  ` + fs2.readFileSync(fullCssPath, "utf-8");
1136
- }
1137
1524
  }
1138
1525
  }
1139
1526
  const inlinedCss = doc.inlinedStyles.join("\n");
1140
1527
  let bodyHtml = "";
1141
- if (metadata.title) {
1528
+ if (resolved.title) {
1142
1529
  bodyHtml += ` <header class="document-header">
1143
1530
  `;
1144
- bodyHtml += ` <h1 class="document-title">${escapeHtml(metadata.title)}</h1>
1531
+ bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1145
1532
  `;
1146
- if (metadata.subtitle) {
1147
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(metadata.subtitle)}</div>
1533
+ if (resolved.subtitle) {
1534
+ bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1148
1535
  `;
1149
1536
  }
1150
- if (metadata.author || metadata.date) {
1537
+ if (resolved.author || resolved.date || resolved.version) {
1151
1538
  bodyHtml += ` <div class="document-meta">
1152
1539
  `;
1153
- if (metadata.author) {
1154
- const authors = Array.isArray(metadata.author) ? metadata.author.join(", ") : metadata.author;
1155
- bodyHtml += ` <span>Author: ${escapeHtml(authors)}</span>
1540
+ if (resolved.author) {
1541
+ bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1156
1542
  `;
1157
1543
  }
1158
- if (metadata.date) {
1159
- bodyHtml += ` <span>Date: ${escapeHtml(metadata.date)}</span>
1544
+ if (resolved.version) {
1545
+ bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1546
+ `;
1547
+ }
1548
+ if (resolved.date) {
1549
+ bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1160
1550
  `;
1161
1551
  }
1162
1552
  bodyHtml += ` </div>
@@ -1165,22 +1555,20 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1165
1555
  bodyHtml += ` </header>
1166
1556
  `;
1167
1557
  }
1168
- if (config.toc || metadata.toc) {
1169
- if (doc.tocEntries.length > 0) {
1170
- bodyHtml += ` <nav class="table-of-contents">
1558
+ if (resolved.toc && doc.tocEntries.length > 0) {
1559
+ bodyHtml += ` <nav class="table-of-contents">
1171
1560
  `;
1172
- bodyHtml += ` <h2>Table of Contents</h2>
1561
+ bodyHtml += ` <h2>Table of Contents</h2>
1173
1562
  <ul>
1174
1563
  `;
1175
- for (const entry of doc.tocEntries) {
1176
- const indent = " ".repeat(entry.level);
1177
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1564
+ for (const entry of doc.tocEntries) {
1565
+ const indent = " ".repeat(entry.level);
1566
+ bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1178
1567
  `;
1179
- }
1180
- bodyHtml += ` </ul>
1568
+ }
1569
+ bodyHtml += ` </ul>
1181
1570
  </nav>
1182
1571
  `;
1183
- }
1184
1572
  }
1185
1573
  for (const node of doc.nodes) {
1186
1574
  if (node.type === "heading") {
@@ -1198,7 +1586,7 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1198
1586
  if (node.type === "codeBlock") {
1199
1587
  const lang = node.language || "";
1200
1588
  const langClass = lang ? ` class="language-${escapeHtml(lang)}"` : "";
1201
- const highlighted = highlightCodeToHtml(node.text || "", lang);
1589
+ const highlighted = highlightCodeToHtml(node.text || "", lang, resolved.syntaxTheme);
1202
1590
  bodyHtml += ` <pre><code${langClass}>${highlighted}</code></pre>
1203
1591
  `;
1204
1592
  continue;
@@ -1242,15 +1630,12 @@ ${escapeHtml(node.text || "")}
1242
1630
  for (const row of node.children) {
1243
1631
  bodyHtml += ` <tr>
1244
1632
  `;
1245
- if (row.children) {
1246
- for (let colIdx = 0; colIdx < row.children.length; colIdx++) {
1247
- const cell = row.children[colIdx];
1248
- const tag = row.isHeader ? "th" : "td";
1249
- const align = ((_a = node.align) == null ? void 0 : _a[colIdx]) ? ` style="text-align: ${node.align[colIdx]}"` : "";
1250
- const cellInner = await renderInlinesToHtml(cell.inlines, baseDir);
1251
- bodyHtml += ` <${tag}${align}>${cellInner}</${tag}>
1633
+ for (const cell of row.children || []) {
1634
+ const tag = cell.isHeader ? "th" : "td";
1635
+ const align = cell.align ? ` align="${cell.align}"` : "";
1636
+ const inner = await renderInlinesToHtml(cell.inlines, baseDir);
1637
+ bodyHtml += ` <${tag}${align}>${inner}</${tag}>
1252
1638
  `;
1253
- }
1254
1639
  }
1255
1640
  bodyHtml += ` </tr>
1256
1641
  `;
@@ -1264,57 +1649,186 @@ ${escapeHtml(node.text || "")}
1264
1649
  bodyHtml += ` <${tag}>
1265
1650
  `;
1266
1651
  for (const item of node.children) {
1267
- const itemInner = await renderInlinesToHtml(item.inlines, baseDir);
1268
- let prefix = "";
1269
- if (item.checked !== void 0) {
1270
- prefix = `<input type="checkbox" disabled ${item.checked ? "checked" : ""}/> `;
1271
- }
1272
- bodyHtml += ` <li>${prefix}${itemInner}</li>
1652
+ const inner = await renderInlinesToHtml(item.inlines, baseDir);
1653
+ bodyHtml += ` <li>${inner}</li>
1273
1654
  `;
1274
1655
  }
1275
1656
  bodyHtml += ` </${tag}>
1276
1657
  `;
1277
1658
  continue;
1278
1659
  }
1279
- if (node.type === "htmlBlock") {
1280
- bodyHtml += ` ${node.rawHtml}
1660
+ if (node.type === "thematicBreak") {
1661
+ bodyHtml += ` <hr />
1281
1662
  `;
1282
1663
  continue;
1283
1664
  }
1284
- if (node.type === "thematicBreak") {
1285
- bodyHtml += ` <hr />
1665
+ if (node.type === "htmlBlock") {
1666
+ bodyHtml += ` ${node.text}
1286
1667
  `;
1287
1668
  continue;
1288
1669
  }
1289
1670
  }
1290
- const wmConfig = config.watermark ?? metadata.watermark;
1291
- let watermarkHtml = "";
1292
1671
  let watermarkCss = "";
1293
- if (wmConfig) {
1294
- const wmText = typeof wmConfig === "string" ? wmConfig : wmConfig.text;
1295
- const opacity = typeof wmConfig === "object" && wmConfig.opacity !== void 0 ? wmConfig.opacity : 0.12;
1296
- const rotate = typeof wmConfig === "object" && wmConfig.rotate !== void 0 ? wmConfig.rotate : -45;
1297
- const color = typeof wmConfig === "object" && wmConfig.color ? wmConfig.color : "#94A3B8";
1298
- const fontSize = typeof wmConfig === "object" && wmConfig.fontSize ? `${wmConfig.fontSize}pt` : "52pt";
1672
+ let watermarkHtml = "";
1673
+ if (resolved.watermark) {
1674
+ const wm = resolved.watermark;
1299
1675
  watermarkCss = `
1300
1676
  .document-watermark {
1301
1677
  position: fixed;
1302
- top: 50%;
1303
- left: 50%;
1304
- transform: translate(-50%, -50%) rotate(${rotate}deg);
1305
- font-size: ${fontSize};
1306
- font-weight: 800;
1307
- color: ${color};
1308
- opacity: ${opacity};
1678
+ top: 0;
1679
+ left: 0;
1680
+ width: 100vw;
1681
+ height: 100vh;
1309
1682
  pointer-events: none;
1683
+ z-index: 0;
1310
1684
  user-select: none;
1311
- z-index: 9999;
1312
- text-transform: uppercase;
1313
- letter-spacing: 0.15em;
1314
- white-space: nowrap;
1685
+ -webkit-user-select: none;
1686
+ }
1687
+ .document-container {
1688
+ position: relative;
1689
+ z-index: 1;
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
+ }
1702
+ `;
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
+ }
1315
1805
  }
1316
1806
  `;
1317
- watermarkHtml = ` <div class="document-watermark">${escapeHtml(wmText)}</div>
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>
1318
1832
  `;
1319
1833
  }
1320
1834
  const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
@@ -1333,24 +1847,24 @@ ${escapeHtml(node.text || "")}
1333
1847
  }
1334
1848
  });
1335
1849
  </script>` : "";
1336
- const documentTitle = metadata.title || "MarkForge Document";
1337
1850
  return `<!DOCTYPE html>
1338
- <html lang="${metadata.lang || "en"}">
1851
+ <html lang="${resolved.lang}">
1339
1852
  <head>
1340
1853
  <meta charset="UTF-8">
1341
1854
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1342
- <title>${escapeHtml(documentTitle)}</title>
1855
+ <title>${escapeHtml(resolved.title)}</title>
1343
1856
  <style>
1344
1857
  ${THEME_COMPONENTS}
1345
1858
  ${baseThemeCss}
1346
1859
  ${customCss}
1347
1860
  ${inlinedCss}
1348
1861
  ${watermarkCss}
1862
+ ${signaturesCss}
1349
1863
  </style>
1350
1864
  </head>
1351
1865
  <body>
1352
1866
  ${watermarkHtml} <div class="document-container">
1353
- ${bodyHtml} </div>
1867
+ ${bodyHtml}${signaturesHtml} </div>
1354
1868
  ${mermaidScript}
1355
1869
  </body>
1356
1870
  </html>`;
@@ -1383,14 +1897,14 @@ function findChromeExecutable() {
1383
1897
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
1384
1898
  "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
1385
1899
  "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
1900
+ // Windows — Microsoft Edge (Native Windows 10/11 browser, enterprise whitelist friendly)
1901
+ `${winProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
1902
+ `${winProgramFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
1903
+ `${winLocalAppData}\\Microsoft\\Edge\\Application\\msedge.exe`,
1386
1904
  // Windows — Google Chrome
1387
1905
  `${winProgramFiles}\\Google\\Chrome\\Application\\chrome.exe`,
1388
1906
  `${winProgramFilesX86}\\Google\\Chrome\\Application\\chrome.exe`,
1389
1907
  `${winLocalAppData}\\Google\\Chrome\\Application\\chrome.exe`,
1390
- // Windows — Microsoft Edge (ships with Windows 10/11)
1391
- `${winProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
1392
- `${winProgramFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
1393
- `${winLocalAppData}\\Microsoft\\Edge\\Application\\msedge.exe`,
1394
1908
  // Windows — Brave Browser
1395
1909
  `${winProgramFiles}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
1396
1910
  `${winProgramFilesX86}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
@@ -1421,22 +1935,50 @@ function findChromeExecutable() {
1421
1935
  return null;
1422
1936
  }
1423
1937
  function injectPagedMediaStyles(html, config, metadata) {
1424
- var _a, _b, _c, _d;
1425
- const merged = { ...config.metadata, ...metadata };
1426
- const orientation = merged.orientation || config.orientation || "portrait";
1427
- const size = merged.paperSize || config.paperSize || "A4";
1428
- const margins = merged.margins || config.margins || {};
1429
- const top = margins.top || ((_a = config.margins) == null ? void 0 : _a.top) || "2.5cm";
1430
- const bottom = margins.bottom || ((_b = config.margins) == null ? void 0 : _b.bottom) || "2.5cm";
1431
- const left = margins.left || ((_c = config.margins) == null ? void 0 : _c.left) || "2.5cm";
1432
- const right = margins.right || ((_d = config.margins) == null ? void 0 : _d.right) || "2.5cm";
1433
- const headerCfg = merged.header;
1434
- const footerCfg = merged.footer;
1435
- const headerLeft = (headerCfg == null ? void 0 : headerCfg.left) ?? "";
1436
- const headerCenter = (headerCfg == null ? void 0 : headerCfg.center) ?? "";
1437
- const headerRight = (headerCfg == null ? void 0 : headerCfg.right) ?? "";
1438
- const footerLeft = ((footerCfg == null ? void 0 : footerCfg.left) ?? "").replace("{page}", "").replace("{pages}", "").trim();
1439
- const esc = (s) => s.replace(/"/g, '"').replace(/\\/g, "\\\\");
1938
+ var _a, _b, _c, _d, _e, _f;
1939
+ const resolved = resolveDocumentConfig(metadata || {}, config);
1940
+ const size = resolved.paperSize;
1941
+ const orientation = resolved.orientation;
1942
+ const top = resolved.margins.top;
1943
+ const bottom = resolved.margins.bottom;
1944
+ const left = resolved.margins.left;
1945
+ const right = resolved.margins.right;
1946
+ const esc = (s) => s.replace(/"/g, '\\"').replace(/\\/g, "\\\\");
1947
+ const buildZoneCss = (pos, zone, isPageCounter = false) => {
1948
+ if (!zone && !isPageCounter) return "";
1949
+ const color = (zone == null ? void 0 : zone.color) || "#94a3b8";
1950
+ const fontSize = (zone == null ? void 0 : zone.fontSize) ? `${zone.fontSize}pt` : "9pt";
1951
+ const fontFamily = (zone == null ? void 0 : zone.fontFamily) ? `font-family: ${zone.fontFamily};` : "";
1952
+ const fontWeight = (zone == null ? void 0 : zone.bold) ? "font-weight: bold;" : "";
1953
+ const fontStyle = (zone == null ? void 0 : zone.italic) ? "font-style: italic;" : "";
1954
+ let content = "";
1955
+ if (isPageCounter) {
1956
+ if ((zone == null ? void 0 : zone.text) && (zone.text.includes("{page}") || zone.text.includes("{pages}"))) {
1957
+ const parts = zone.text.split(/(\{page\}|\{pages\})/gi);
1958
+ const cssParts = parts.map((part) => {
1959
+ if (part.toLowerCase() === "{page}") return "counter(page)";
1960
+ if (part.toLowerCase() === "{pages}") return "counter(pages)";
1961
+ return `"${esc(part)}"`;
1962
+ });
1963
+ content = cssParts.join(" ");
1964
+ } else if (zone == null ? void 0 : zone.text) {
1965
+ content = `"${esc(zone.text)}"`;
1966
+ } else {
1967
+ content = `"Page " counter(page) " of " counter(pages)`;
1968
+ }
1969
+ } else if (zone == null ? void 0 : zone.text) {
1970
+ content = `"${esc(zone.text)}"`;
1971
+ }
1972
+ if (!content) return "";
1973
+ return `@${pos} {
1974
+ content: ${content};
1975
+ font-size: ${fontSize};
1976
+ color: ${color};
1977
+ ${fontFamily}
1978
+ ${fontWeight}
1979
+ ${fontStyle}
1980
+ }`;
1981
+ };
1440
1982
  const pagedCss = `
1441
1983
  @page {
1442
1984
  size: ${size} ${orientation};
@@ -1444,15 +1986,12 @@ function injectPagedMediaStyles(html, config, metadata) {
1444
1986
  margin-bottom: ${bottom};
1445
1987
  margin-left: ${left};
1446
1988
  margin-right: ${right};
1447
- ${headerLeft ? `@top-left { content: "${esc(headerLeft)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1448
- ${headerCenter ? `@top-center { content: "${esc(headerCenter)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1449
- ${headerRight ? `@top-right { content: "${esc(headerRight)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1450
- ${footerLeft ? `@bottom-left { content: "${esc(footerLeft)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1451
- @bottom-right {
1452
- content: "Page " counter(page) " of " counter(pages);
1453
- font-size: 9pt;
1454
- color: #94a3b8;
1455
- }
1989
+ ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
1990
+ ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
1991
+ ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
1992
+ ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
1993
+ ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
1994
+ ${buildZoneCss("bottom-right", (_f = resolved.footer) == null ? void 0 : _f.right, true)}
1456
1995
  }
1457
1996
  @media print {
1458
1997
  body { padding: 0; }
@@ -1509,8 +2048,35 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1509
2048
  if (chromePath) {
1510
2049
  const tmpId = Math.random().toString(36).substring(2, 9);
1511
2050
  const tmpDir = os.tmpdir();
1512
- const tmpHtml = path3.join(tmpDir, `markforge_${tmpId}.html`);
1513
- const tmpPdf = path3.join(tmpDir, `markforge_${tmpId}.pdf`);
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}`);
2054
+ const isolatedFlags = [
2055
+ `--user-data-dir=${tmpProfile}`,
2056
+ "--no-first-run",
2057
+ "--no-default-browser-check",
2058
+ "--disable-sync",
2059
+ "--disable-background-networking",
2060
+ "--disable-component-update",
2061
+ "--disable-default-apps",
2062
+ "--disable-extensions",
2063
+ "--disable-domain-reliability",
2064
+ "--disable-client-side-phishing-detection",
2065
+ "--disable-breakpad",
2066
+ "--disable-component-extensions-with-background-pages",
2067
+ "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
2068
+ "--password-store=basic",
2069
+ "--use-mock-keychain",
2070
+ "--mute-audio",
2071
+ "--no-service-autorun",
2072
+ "--disable-gpu",
2073
+ "--no-sandbox",
2074
+ "--disable-setuid-sandbox",
2075
+ "--allow-file-access-from-files",
2076
+ "--disable-web-security",
2077
+ "--force-color-profile=srgb",
2078
+ "--no-pdf-header-footer"
2079
+ ];
1514
2080
  try {
1515
2081
  fs3.writeFileSync(tmpHtml, pagedHtml, "utf-8");
1516
2082
  const fileUrl = pathToFileURL(tmpHtml).href;
@@ -1518,15 +2084,9 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1518
2084
  chromePath,
1519
2085
  [
1520
2086
  "--headless=new",
1521
- "--disable-gpu",
1522
- "--no-sandbox",
1523
- "--disable-setuid-sandbox",
1524
- "--allow-file-access-from-files",
1525
- "--disable-web-security",
1526
- "--force-color-profile=srgb",
2087
+ ...isolatedFlags,
1527
2088
  "--run-all-compositor-stages-before-draw",
1528
2089
  "--virtual-time-budget=8000",
1529
- "--no-pdf-header-footer",
1530
2090
  `--print-to-pdf=${tmpPdf}`,
1531
2091
  fileUrl
1532
2092
  ],
@@ -1537,13 +2097,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1537
2097
  chromePath,
1538
2098
  [
1539
2099
  "--headless",
1540
- "--disable-gpu",
1541
- "--no-sandbox",
1542
- "--disable-setuid-sandbox",
1543
- "--allow-file-access-from-files",
1544
- "--disable-web-security",
1545
- "--force-color-profile=srgb",
1546
- "--no-pdf-header-footer",
2100
+ ...isolatedFlags,
1547
2101
  `--print-to-pdf=${tmpPdf}`,
1548
2102
  fileUrl
1549
2103
  ],
@@ -1559,6 +2113,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1559
2113
  try {
1560
2114
  if (fs3.existsSync(tmpHtml)) fs3.unlinkSync(tmpHtml);
1561
2115
  if (fs3.existsSync(tmpPdf)) fs3.unlinkSync(tmpPdf);
2116
+ if (fs3.existsSync(tmpProfile)) fs3.rmSync(tmpProfile, { recursive: true, force: true });
1562
2117
  } catch {
1563
2118
  }
1564
2119
  }
@@ -1574,8 +2129,8 @@ async function renderMermaidToPng(mermaidCode, _baseDir = process.cwd()) {
1574
2129
  }
1575
2130
  const tmpId = Math.random().toString(36).substring(2, 9);
1576
2131
  const tmpDir = os2.tmpdir();
1577
- const tmpHtml = path4.join(tmpDir, `mermaid_${tmpId}.html`);
1578
- 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`);
1579
2134
  const htmlContent = `<!DOCTYPE html>
1580
2135
  <html>
1581
2136
  <head>
@@ -1649,30 +2204,7 @@ ${mermaidCode}
1649
2204
  }
1650
2205
 
1651
2206
  // src/core/docx/docxBuilder.ts
1652
- function parseMarginToTwip(margin, defaultTwip = 1440) {
1653
- if (typeof margin === "number") return margin;
1654
- if (!margin) return defaultTwip;
1655
- const str = margin.trim().toLowerCase();
1656
- if (str.endsWith("cm")) {
1657
- const cm = parseFloat(str);
1658
- return Math.round(convertMillimetersToTwip(cm * 10));
1659
- }
1660
- if (str.endsWith("mm")) {
1661
- const mm = parseFloat(str);
1662
- return Math.round(convertMillimetersToTwip(mm));
1663
- }
1664
- if (str.endsWith("in") || str.endsWith("inch")) {
1665
- const inch = parseFloat(str);
1666
- return Math.round(convertInchesToTwip(inch));
1667
- }
1668
- if (str.endsWith("pt")) {
1669
- const pt = parseFloat(str);
1670
- return Math.round(pt * 20);
1671
- }
1672
- const val = parseFloat(str);
1673
- return isNaN(val) ? defaultTwip : Math.round(val);
1674
- }
1675
- async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
2207
+ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), options = {}) {
1676
2208
  const runs = [];
1677
2209
  for (const span of spans) {
1678
2210
  if (span.type === "image" && span.url) {
@@ -1702,7 +2234,10 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1702
2234
  new TextRun({
1703
2235
  text: span.content,
1704
2236
  style: "Hyperlink",
1705
- color: "0969DA",
2237
+ color: "009DA0",
2238
+ font: options.font,
2239
+ size: options.size,
2240
+ bold: options.bold,
1706
2241
  underline: {}
1707
2242
  })
1708
2243
  ],
@@ -1715,7 +2250,11 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1715
2250
  runs.push(
1716
2251
  new TextRun({
1717
2252
  text: span.content,
1718
- bold: true
2253
+ bold: true,
2254
+ font: options.font,
2255
+ size: options.size,
2256
+ color: options.color,
2257
+ italics: options.italics
1719
2258
  })
1720
2259
  );
1721
2260
  continue;
@@ -1724,7 +2263,11 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1724
2263
  runs.push(
1725
2264
  new TextRun({
1726
2265
  text: span.content,
1727
- italics: true
2266
+ italics: true,
2267
+ font: options.font,
2268
+ size: options.size,
2269
+ color: options.color,
2270
+ bold: options.bold
1728
2271
  })
1729
2272
  );
1730
2273
  continue;
@@ -1733,7 +2276,10 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1733
2276
  runs.push(
1734
2277
  new TextRun({
1735
2278
  text: span.content,
1736
- strike: true
2279
+ strike: true,
2280
+ font: options.font,
2281
+ size: options.size,
2282
+ color: options.color
1737
2283
  })
1738
2284
  );
1739
2285
  continue;
@@ -1741,22 +2287,23 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1741
2287
  if (span.type === "code") {
1742
2288
  runs.push(
1743
2289
  new TextRun({
1744
- text: ` ${span.content} `,
2290
+ text: span.content,
1745
2291
  font: "Consolas",
2292
+ size: options.size ? options.size - 2 : 19,
2293
+ color: "0F172A",
1746
2294
  shading: {
1747
2295
  type: ShadingType.CLEAR,
1748
- fill: "F1F5F9",
1749
- color: "0F172A"
2296
+ fill: "F1F5F9"
1750
2297
  }
1751
2298
  })
1752
2299
  );
1753
2300
  continue;
1754
2301
  }
1755
2302
  if (span.type === "htmlInline") {
1756
- let colorHex;
2303
+ let colorHex = options.color;
1757
2304
  let bgHex;
1758
- let isBold = false;
1759
- let isItalic = false;
2305
+ let isBold = !!options.bold;
2306
+ let isItalic = !!options.italics;
1760
2307
  if (span.style) {
1761
2308
  if (span.style.color) {
1762
2309
  colorHex = span.style.color.replace("#", "").trim();
@@ -1772,13 +2319,9 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1772
2319
  }
1773
2320
  }
1774
2321
  if (span.children && span.children.length > 0) {
1775
- const childRuns = await convertInlinesToTextRuns(span.children, baseDir);
2322
+ const childRuns = await convertInlinesToTextRuns(span.children, baseDir, options);
1776
2323
  for (const child of childRuns) {
1777
- if (child instanceof TextRun) {
1778
- runs.push(child);
1779
- } else {
1780
- runs.push(child);
1781
- }
2324
+ runs.push(child);
1782
2325
  }
1783
2326
  continue;
1784
2327
  }
@@ -1788,6 +2331,8 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1788
2331
  color: colorHex,
1789
2332
  bold: isBold,
1790
2333
  italics: isItalic,
2334
+ font: options.font,
2335
+ size: options.size,
1791
2336
  shading: bgHex ? { type: ShadingType.CLEAR, fill: bgHex } : void 0
1792
2337
  })
1793
2338
  );
@@ -1795,91 +2340,239 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1795
2340
  }
1796
2341
  runs.push(
1797
2342
  new TextRun({
1798
- text: span.content
2343
+ text: span.content,
2344
+ font: options.font,
2345
+ size: options.size,
2346
+ color: options.color,
2347
+ bold: options.bold,
2348
+ italics: options.italics
1799
2349
  })
1800
2350
  );
1801
2351
  }
1802
2352
  return runs;
1803
2353
  }
1804
2354
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1805
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1806
- const metadata = { ...config.metadata, ...doc.metadata };
2355
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2356
+ const resolved = resolveDocumentConfig(doc.metadata, config);
1807
2357
  const docElements = [];
1808
- if (metadata.title) {
2358
+ const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
2359
+ const primaryHex = (themeProps.primaryColor || "#33CDCF").replace("#", "");
2360
+ const primaryDarkHex = (themeProps.primaryDark || "#009DA0").replace("#", "");
2361
+ const textHex = (themeProps.textColor || "#0F172A").replace("#", "");
2362
+ const textMutedHex = (themeProps.textMuted || "#64748B").replace("#", "");
2363
+ const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2364
+ const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2365
+ const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2366
+ if (resolved.title) {
1809
2367
  docElements.push(
1810
2368
  new Paragraph({
1811
- text: metadata.title,
1812
- heading: HeadingLevel.TITLE,
1813
- spacing: { before: 200, after: 120 }
2369
+ children: [
2370
+ new TextRun({
2371
+ text: resolved.title,
2372
+ bold: true,
2373
+ size: 44,
2374
+ // 22pt
2375
+ color: textHex,
2376
+ font: defaultFont
2377
+ })
2378
+ ],
2379
+ spacing: { before: 120, after: 80 }
1814
2380
  })
1815
2381
  );
1816
- if (metadata.subtitle) {
2382
+ if (resolved.subtitle) {
1817
2383
  docElements.push(
1818
2384
  new Paragraph({
1819
2385
  children: [
1820
2386
  new TextRun({
1821
- text: metadata.subtitle,
1822
- italics: true,
1823
- color: "64748B",
1824
- size: 24
2387
+ text: resolved.subtitle,
2388
+ color: textMutedHex,
2389
+ size: 24,
1825
2390
  // 12pt
2391
+ font: defaultFont
1826
2392
  })
1827
2393
  ],
1828
- spacing: { after: 180 }
2394
+ spacing: { before: 60, after: 120 }
1829
2395
  })
1830
2396
  );
1831
2397
  }
1832
- if (metadata.author || metadata.date) {
2398
+ if (resolved.author || resolved.date || resolved.version || resolved.company) {
1833
2399
  const metaParts = [];
1834
- if (metadata.author) metaParts.push(`Author: ${Array.isArray(metadata.author) ? metadata.author.join(", ") : metadata.author}`);
1835
- if (metadata.date) metaParts.push(`Date: ${metadata.date}`);
2400
+ if (resolved.author) metaParts.push(`Author: ${resolved.author}`);
2401
+ if (resolved.version) metaParts.push(`Version: ${resolved.version}`);
2402
+ if (resolved.date) metaParts.push(`Date: ${resolved.date}`);
1836
2403
  docElements.push(
1837
2404
  new Paragraph({
1838
2405
  children: [
1839
2406
  new TextRun({
1840
- text: metaParts.join(" | "),
1841
- color: "94A3B8",
1842
- size: 20
1843
- // 10pt
2407
+ text: metaParts.join(" "),
2408
+ color: textMutedHex,
2409
+ size: 18,
2410
+ // 9pt
2411
+ font: defaultFont
1844
2412
  })
1845
2413
  ],
1846
- spacing: { after: 360 },
2414
+ spacing: { before: 40, after: 240 },
1847
2415
  border: {
1848
2416
  bottom: {
1849
- color: "E2E8F0",
1850
- space: 10,
2417
+ color: borderHex,
2418
+ space: 12,
1851
2419
  style: BorderStyle.SINGLE,
1852
- size: 6
2420
+ size: 4
1853
2421
  }
1854
2422
  }
1855
2423
  })
1856
2424
  );
1857
2425
  }
1858
2426
  }
1859
- for (const node of doc.nodes) {
1860
- if (node.type === "heading") {
1861
- let headingLevel = HeadingLevel.HEADING_1;
1862
- if (node.level === 2) headingLevel = HeadingLevel.HEADING_2;
1863
- if (node.level === 3) headingLevel = HeadingLevel.HEADING_3;
1864
- if (node.level === 4) headingLevel = HeadingLevel.HEADING_4;
1865
- if (node.level === 5) headingLevel = HeadingLevel.HEADING_5;
1866
- if (node.level === 6) headingLevel = HeadingLevel.HEADING_6;
1867
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
1868
- docElements.push(
2427
+ if (resolved.toc) {
2428
+ const headingNodes = doc.nodes.filter(
2429
+ (n) => n.type === "heading" && typeof n.level === "number" && n.level >= 1 && n.level <= 3
2430
+ );
2431
+ if (headingNodes.length > 0) {
2432
+ const tocParagraphs = [
1869
2433
  new Paragraph({
1870
- heading: headingLevel,
1871
- children: runs,
1872
- spacing: { before: 240, after: 120 }
2434
+ children: [
2435
+ new TextRun({
2436
+ text: "TABLE OF CONTENTS",
2437
+ bold: true,
2438
+ size: 18,
2439
+ // 9pt
2440
+ color: textMutedHex,
2441
+ font: defaultFont
2442
+ })
2443
+ ],
2444
+ spacing: { after: 120 },
2445
+ border: {
2446
+ bottom: {
2447
+ color: borderHex,
2448
+ space: 6,
2449
+ style: BorderStyle.SINGLE,
2450
+ size: 4
2451
+ }
2452
+ }
1873
2453
  })
1874
- );
2454
+ ];
2455
+ for (const h of headingNodes) {
2456
+ const indentLeft = (h.level - 1) * 240;
2457
+ tocParagraphs.push(
2458
+ new Paragraph({
2459
+ indent: { left: indentLeft },
2460
+ children: [
2461
+ new TextRun({
2462
+ text: h.text,
2463
+ bold: h.level === 1,
2464
+ color: primaryDarkHex,
2465
+ size: h.level === 1 ? 21 : 20,
2466
+ font: defaultFont
2467
+ })
2468
+ ],
2469
+ spacing: { before: 20, after: 20 }
2470
+ })
2471
+ );
2472
+ }
2473
+ const tocCard = new Table({
2474
+ width: { size: 100, type: WidthType.PERCENTAGE },
2475
+ columnWidths: [9e3],
2476
+ rows: [
2477
+ new TableRow({
2478
+ children: [
2479
+ new TableCell({
2480
+ width: { size: 9e3, type: WidthType.DXA },
2481
+ shading: { fill: cardBgHex, type: ShadingType.CLEAR },
2482
+ margins: { top: 140, bottom: 140, left: 180, right: 180 },
2483
+ borders: {
2484
+ top: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
2485
+ bottom: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
2486
+ left: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
2487
+ right: { style: BorderStyle.SINGLE, size: 4, color: borderHex }
2488
+ },
2489
+ children: tocParagraphs
2490
+ })
2491
+ ]
2492
+ })
2493
+ ]
2494
+ });
2495
+ docElements.push(tocCard);
2496
+ docElements.push(new Paragraph({ spacing: { after: 200 } }));
2497
+ }
2498
+ }
2499
+ for (const node of doc.nodes) {
2500
+ if (node.type === "heading") {
2501
+ if (node.level === 1) {
2502
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2503
+ font: defaultFont,
2504
+ size: 34,
2505
+ // 17pt
2506
+ color: textHex,
2507
+ bold: true
2508
+ });
2509
+ docElements.push(
2510
+ new Paragraph({
2511
+ heading: HeadingLevel.HEADING_1,
2512
+ children: runs,
2513
+ spacing: { before: 360, after: 140 },
2514
+ border: {
2515
+ bottom: {
2516
+ color: primaryHex,
2517
+ space: 6,
2518
+ style: BorderStyle.SINGLE,
2519
+ size: 16
2520
+ }
2521
+ }
2522
+ })
2523
+ );
2524
+ } else if (node.level === 2) {
2525
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2526
+ font: defaultFont,
2527
+ size: 26,
2528
+ // 13pt
2529
+ color: primaryDarkHex,
2530
+ bold: true
2531
+ });
2532
+ docElements.push(
2533
+ new Paragraph({
2534
+ heading: HeadingLevel.HEADING_2,
2535
+ children: runs,
2536
+ spacing: { before: 280, after: 100 },
2537
+ border: {
2538
+ bottom: {
2539
+ color: borderHex,
2540
+ space: 4,
2541
+ style: BorderStyle.SINGLE,
2542
+ size: 4
2543
+ }
2544
+ }
2545
+ })
2546
+ );
2547
+ } else {
2548
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2549
+ font: defaultFont,
2550
+ size: 22,
2551
+ // 11pt
2552
+ color: textHex,
2553
+ bold: true
2554
+ });
2555
+ docElements.push(
2556
+ new Paragraph({
2557
+ heading: node.level === 3 ? HeadingLevel.HEADING_3 : HeadingLevel.HEADING_4,
2558
+ children: runs,
2559
+ spacing: { before: 200, after: 80 }
2560
+ })
2561
+ );
2562
+ }
1875
2563
  continue;
1876
2564
  }
1877
2565
  if (node.type === "paragraph") {
1878
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
2566
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2567
+ font: defaultFont,
2568
+ size: 22,
2569
+ // 11pt
2570
+ color: "334155"
2571
+ });
1879
2572
  docElements.push(
1880
2573
  new Paragraph({
1881
2574
  children: runs,
1882
- spacing: { before: 60, after: 140 }
2575
+ spacing: { before: 40, after: 140, line: 280 }
1883
2576
  })
1884
2577
  );
1885
2578
  continue;
@@ -1951,7 +2644,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1951
2644
  bgFill = "F5F3FF";
1952
2645
  title = "IMPORTANT";
1953
2646
  }
1954
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
2647
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2648
+ font: defaultFont,
2649
+ size: 21,
2650
+ color: "334155"
2651
+ });
1955
2652
  const calloutTable = new Table({
1956
2653
  width: { size: 100, type: WidthType.PERCENTAGE },
1957
2654
  columnWidths: [9e3],
@@ -1966,7 +2663,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1966
2663
  top: { style: BorderStyle.NONE },
1967
2664
  bottom: { style: BorderStyle.NONE },
1968
2665
  right: { style: BorderStyle.NONE },
1969
- left: { style: BorderStyle.SINGLE, size: 16, color: borderColor }
2666
+ left: { style: BorderStyle.SINGLE, size: 20, color: borderColor }
1970
2667
  },
1971
2668
  children: [
1972
2669
  new Paragraph({
@@ -1975,13 +2672,15 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1975
2672
  text: `[${title}]`,
1976
2673
  bold: true,
1977
2674
  color: borderColor,
2675
+ font: defaultFont,
1978
2676
  size: 20
1979
2677
  })
1980
2678
  ],
1981
- spacing: { after: 60 }
2679
+ spacing: { after: 40 }
1982
2680
  }),
1983
2681
  new Paragraph({
1984
- children: runs
2682
+ children: runs,
2683
+ spacing: { line: 270 }
1985
2684
  })
1986
2685
  ]
1987
2686
  })
@@ -1999,17 +2698,27 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1999
2698
  if (lines.length > 0) {
2000
2699
  for (const lineText of lines) {
2001
2700
  const spans = parseInlineSpans(lineText.replace(/^>\s?/, "").trim());
2002
- const lineRuns = await convertInlinesToTextRuns(spans, baseDir);
2701
+ const lineRuns = await convertInlinesToTextRuns(spans, baseDir, {
2702
+ font: defaultFont,
2703
+ size: 21,
2704
+ color: "475569",
2705
+ italics: true
2706
+ });
2003
2707
  quoteParagraphs.push(
2004
2708
  new Paragraph({
2005
2709
  children: lineRuns,
2006
- spacing: { before: 40, after: 40 }
2710
+ spacing: { before: 20, after: 20, line: 260 }
2007
2711
  })
2008
2712
  );
2009
2713
  }
2010
2714
  } else {
2011
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
2012
- quoteParagraphs.push(new Paragraph({ children: runs }));
2715
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2716
+ font: defaultFont,
2717
+ size: 21,
2718
+ color: "475569",
2719
+ italics: true
2720
+ });
2721
+ quoteParagraphs.push(new Paragraph({ children: runs, spacing: { line: 260 } }));
2013
2722
  }
2014
2723
  const quoteTable = new Table({
2015
2724
  width: { size: 100, type: WidthType.PERCENTAGE },
@@ -2025,7 +2734,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2025
2734
  top: { style: BorderStyle.NONE },
2026
2735
  bottom: { style: BorderStyle.NONE },
2027
2736
  right: { style: BorderStyle.NONE },
2028
- left: { style: BorderStyle.SINGLE, size: 12, color: "33CDCF" }
2737
+ left: { style: BorderStyle.SINGLE, size: 16, color: primaryHex }
2029
2738
  },
2030
2739
  children: quoteParagraphs
2031
2740
  })
@@ -2060,7 +2769,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2060
2769
  docElements.push(
2061
2770
  new Paragraph({
2062
2771
  children: [
2063
- new TextRun({ text: "[Mermaid Diagram: " + (node.text || "").slice(0, 40) + "...]", bold: true, color: "33CDCF" })
2772
+ new TextRun({ text: "[Mermaid Diagram: " + (node.text || "").slice(0, 40) + "...]", bold: true, color: primaryHex })
2064
2773
  ],
2065
2774
  spacing: { before: 60, after: 60 }
2066
2775
  })
@@ -2072,8 +2781,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2072
2781
  const tableRows = [];
2073
2782
  const numCols = ((_b = (_a = node.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) || 1;
2074
2783
  const colWidth = Math.floor(9e3 / numCols);
2075
- for (const rowNode of node.children) {
2784
+ for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2785
+ const rowNode = node.children[rowIdx];
2076
2786
  const cells = [];
2787
+ const isHeader = rowNode.isHeader;
2788
+ const rowBg = isHeader ? "F1F5F9" : rowIdx % 2 === 0 ? "FFFFFF" : "F8FAFC";
2077
2789
  if (rowNode.children) {
2078
2790
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2079
2791
  const cellNode = rowNode.children[colIdx];
@@ -2081,22 +2793,27 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2081
2793
  let alignment = AlignmentType.LEFT;
2082
2794
  if (align === "center") alignment = AlignmentType.CENTER;
2083
2795
  if (align === "right") alignment = AlignmentType.RIGHT;
2084
- const runs = await convertInlinesToTextRuns(cellNode.inlines, baseDir);
2796
+ const runs = await convertInlinesToTextRuns(cellNode.inlines, baseDir, {
2797
+ font: defaultFont,
2798
+ size: 20,
2799
+ color: isHeader ? "0F172A" : "334155",
2800
+ bold: isHeader
2801
+ });
2085
2802
  cells.push(
2086
2803
  new TableCell({
2087
2804
  width: { size: colWidth, type: WidthType.DXA },
2088
- shading: rowNode.isHeader ? { fill: "F1F5F9", type: ShadingType.CLEAR } : void 0,
2089
- margins: { top: 100, bottom: 100, left: 120, right: 120 },
2805
+ shading: { fill: rowBg, type: ShadingType.CLEAR },
2806
+ margins: { top: 100, bottom: 100, left: 140, right: 140 },
2090
2807
  borders: {
2091
- top: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" },
2092
- bottom: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" },
2093
- left: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" },
2094
- right: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" }
2808
+ top: { style: BorderStyle.SINGLE, size: 4, color: "E2E8F0" },
2809
+ bottom: { style: BorderStyle.SINGLE, size: isHeader ? 8 : 4, color: isHeader ? "CBD5E1" : "E2E8F0" },
2810
+ left: { style: BorderStyle.SINGLE, size: 4, color: "E2E8F0" },
2811
+ right: { style: BorderStyle.SINGLE, size: 4, color: "E2E8F0" }
2095
2812
  },
2096
2813
  children: [
2097
2814
  new Paragraph({
2098
2815
  alignment,
2099
- children: rowNode.isHeader ? runs.map((r) => r instanceof TextRun ? new TextRun({ ...r, bold: true, color: "0F172A" }) : r) : runs
2816
+ children: runs
2100
2817
  })
2101
2818
  ]
2102
2819
  })
@@ -2105,7 +2822,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2105
2822
  }
2106
2823
  tableRows.push(
2107
2824
  new TableRow({
2108
- tableHeader: rowNode.isHeader,
2825
+ tableHeader: isHeader,
2109
2826
  children: cells
2110
2827
  })
2111
2828
  );
@@ -2121,7 +2838,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2121
2838
  }
2122
2839
  if (node.type === "list" && node.children) {
2123
2840
  for (const item of node.children) {
2124
- const runs = await convertInlinesToTextRuns(item.inlines, baseDir);
2841
+ const runs = await convertInlinesToTextRuns(item.inlines, baseDir, {
2842
+ font: defaultFont,
2843
+ size: 21,
2844
+ color: "334155"
2845
+ });
2125
2846
  let prefix = "";
2126
2847
  if (item.checked !== void 0) {
2127
2848
  prefix = item.checked ? "[X] " : "[ ] ";
@@ -2133,7 +2854,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2133
2854
  ...prefix ? [new TextRun({ text: prefix, bold: true, font: "Consolas" })] : [],
2134
2855
  ...runs
2135
2856
  ],
2136
- spacing: { before: 40, after: 40 }
2857
+ spacing: { before: 20, after: 20, line: 260 }
2137
2858
  })
2138
2859
  );
2139
2860
  }
@@ -2208,88 +2929,258 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2208
2929
  continue;
2209
2930
  }
2210
2931
  }
2211
- const headerObj = metadata.header || config.header;
2212
- const footerObj = metadata.footer || config.footer;
2213
- const headerTabStops = [
2214
- { type: TabStopType.CENTER, position: 4513 },
2215
- { type: TabStopType.RIGHT, position: 9026 }
2216
- ];
2217
- const docHeader = headerObj ? new Header2({
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
+ }
2985
+ const contentWidthTwip = Math.max(
2986
+ 1e3,
2987
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2988
+ );
2989
+ const centerPos = Math.round(contentWidthTwip / 2);
2990
+ const rightPos = contentWidthTwip;
2991
+ const headerRuns = [];
2992
+ if ((_d = resolved.header) == null ? void 0 : _d.left) {
2993
+ headerRuns.push(
2994
+ new TextRun({
2995
+ text: resolved.header.left.text,
2996
+ color: resolved.header.left.color.replace("#", ""),
2997
+ size: (resolved.header.left.fontSize || 9) * 2,
2998
+ font: resolved.header.left.fontFamily || defaultFont,
2999
+ bold: resolved.header.left.bold,
3000
+ italics: resolved.header.left.italic
3001
+ })
3002
+ );
3003
+ }
3004
+ headerRuns.push(new TextRun({ text: " " }));
3005
+ if ((_e = resolved.header) == null ? void 0 : _e.center) {
3006
+ headerRuns.push(
3007
+ new TextRun({
3008
+ text: resolved.header.center.text,
3009
+ color: resolved.header.center.color.replace("#", ""),
3010
+ size: (resolved.header.center.fontSize || 9) * 2,
3011
+ font: resolved.header.center.fontFamily || defaultFont,
3012
+ bold: resolved.header.center.bold,
3013
+ italics: resolved.header.center.italic
3014
+ })
3015
+ );
3016
+ }
3017
+ headerRuns.push(new TextRun({ text: " " }));
3018
+ if ((_f = resolved.header) == null ? void 0 : _f.right) {
3019
+ headerRuns.push(
3020
+ new TextRun({
3021
+ text: resolved.header.right.text,
3022
+ color: resolved.header.right.color.replace("#", ""),
3023
+ size: (resolved.header.right.fontSize || 9) * 2,
3024
+ font: resolved.header.right.fontFamily || defaultFont,
3025
+ bold: resolved.header.right.bold,
3026
+ italics: resolved.header.right.italic
3027
+ })
3028
+ );
3029
+ }
3030
+ const docHeader = resolved.header ? new Header2({
2218
3031
  children: [
2219
3032
  new Paragraph({
2220
- tabStops: headerTabStops,
2221
- children: [
2222
- // Left zone
2223
- ...headerObj.left ? [
3033
+ tabStops: [
3034
+ {
3035
+ type: TabStopType.CENTER,
3036
+ position: centerPos
3037
+ },
3038
+ {
3039
+ type: TabStopType.RIGHT,
3040
+ position: rightPos
3041
+ }
3042
+ ],
3043
+ border: resolved.header.divider ? {
3044
+ bottom: {
3045
+ style: BorderStyle.SINGLE,
3046
+ size: 4,
3047
+ space: 6,
3048
+ color: (resolved.header.dividerColor || "#CBD5E1").replace("#", "")
3049
+ }
3050
+ } : void 0,
3051
+ children: headerRuns,
3052
+ spacing: { after: 120 }
3053
+ })
3054
+ ]
3055
+ }) : void 0;
3056
+ const footerRuns = [];
3057
+ if ((_g = resolved.footer) == null ? void 0 : _g.left) {
3058
+ footerRuns.push(
3059
+ new TextRun({
3060
+ text: resolved.footer.left.text,
3061
+ color: resolved.footer.left.color.replace("#", ""),
3062
+ size: (resolved.footer.left.fontSize || 9) * 2,
3063
+ font: resolved.footer.left.fontFamily || defaultFont,
3064
+ bold: resolved.footer.left.bold,
3065
+ italics: resolved.footer.left.italic
3066
+ })
3067
+ );
3068
+ }
3069
+ footerRuns.push(new TextRun({ text: " " }));
3070
+ if ((_h = resolved.footer) == null ? void 0 : _h.center) {
3071
+ footerRuns.push(
3072
+ new TextRun({
3073
+ text: resolved.footer.center.text,
3074
+ color: resolved.footer.center.color.replace("#", ""),
3075
+ size: (resolved.footer.center.fontSize || 9) * 2,
3076
+ font: resolved.footer.center.fontFamily || defaultFont,
3077
+ bold: resolved.footer.center.bold,
3078
+ italics: resolved.footer.center.italic
3079
+ })
3080
+ );
3081
+ }
3082
+ footerRuns.push(new TextRun({ text: " " }));
3083
+ if ((_i = resolved.footer) == null ? void 0 : _i.right) {
3084
+ const rZone = resolved.footer.right;
3085
+ const rColor = rZone.color.replace("#", "");
3086
+ const rSize = (rZone.fontSize || 9) * 2;
3087
+ const rFont = rZone.fontFamily || defaultFont;
3088
+ const rBold = rZone.bold;
3089
+ const rItalics = rZone.italic;
3090
+ if (rZone.text.includes("{page}") || rZone.text.includes("{pages}")) {
3091
+ const parts = rZone.text.split(/(\{page\}|\{pages\})/gi);
3092
+ for (const part of parts) {
3093
+ if (part.toLowerCase() === "{page}") {
3094
+ footerRuns.push(
2224
3095
  new TextRun({
2225
- text: headerObj.left.replace("{title}", metadata.title || ""),
2226
- color: "94A3B8",
2227
- size: 18
3096
+ children: [PageNumber.CURRENT],
3097
+ color: rColor,
3098
+ size: rSize,
3099
+ font: rFont,
3100
+ bold: rBold,
3101
+ italics: rItalics
2228
3102
  })
2229
- ] : [],
2230
- // Center zone (tab + text)
2231
- ...headerObj.center ? [
2232
- new TextRun({ text: " ", color: "94A3B8", size: 18 }),
3103
+ );
3104
+ } else if (part.toLowerCase() === "{pages}") {
3105
+ footerRuns.push(
2233
3106
  new TextRun({
2234
- text: headerObj.center.replace("{title}", metadata.title || ""),
2235
- color: "94A3B8",
2236
- size: 18
3107
+ children: [PageNumber.TOTAL_PAGES],
3108
+ color: rColor,
3109
+ size: rSize,
3110
+ font: rFont,
3111
+ bold: rBold,
3112
+ italics: rItalics
2237
3113
  })
2238
- ] : [],
2239
- // Right zone (tab + text) — skip extra tab if center already used one
2240
- ...headerObj.right ? [
2241
- new TextRun({
2242
- text: headerObj.left || headerObj.center ? " " : "",
2243
- color: "94A3B8",
2244
- size: 18
2245
- }),
3114
+ );
3115
+ } else if (part) {
3116
+ footerRuns.push(
2246
3117
  new TextRun({
2247
- text: headerObj.right.replace("{title}", metadata.title || ""),
2248
- color: "94A3B8",
2249
- size: 18
3118
+ text: part,
3119
+ color: rColor,
3120
+ size: rSize,
3121
+ font: rFont,
3122
+ bold: rBold,
3123
+ italics: rItalics
2250
3124
  })
2251
- ] : []
2252
- ]
2253
- })
2254
- ]
2255
- }) : void 0;
2256
- const docFooter = footerObj ? new Footer({
3125
+ );
3126
+ }
3127
+ }
3128
+ } else {
3129
+ footerRuns.push(
3130
+ new TextRun({
3131
+ text: rZone.text,
3132
+ color: rColor,
3133
+ size: rSize,
3134
+ font: rFont,
3135
+ bold: rBold,
3136
+ italics: rItalics
3137
+ })
3138
+ );
3139
+ }
3140
+ }
3141
+ const docFooter = resolved.footer ? new Footer({
2257
3142
  children: [
2258
3143
  new Paragraph({
2259
- tabStops: headerTabStops,
2260
- children: [
2261
- // Left zone
2262
- ...footerObj.left ? [
2263
- new TextRun({
2264
- text: footerObj.left.replace("{page}", "").replace("{pages}", "").trim(),
2265
- color: "94A3B8",
2266
- size: 18
2267
- })
2268
- ] : [],
2269
- // Right zone: always includes page number if right is configured or footer exists
2270
- new TextRun({ text: " ", color: "94A3B8", size: 18 }),
2271
- new TextRun({ text: "Page ", color: "94A3B8", size: 18 }),
2272
- new TextRun({ children: [PageNumber.CURRENT], color: "94A3B8", size: 18 }),
2273
- new TextRun({ text: " of ", color: "94A3B8", size: 18 }),
2274
- new TextRun({ children: [PageNumber.TOTAL_PAGES], color: "94A3B8", size: 18 })
2275
- ]
3144
+ tabStops: [
3145
+ {
3146
+ type: TabStopType.CENTER,
3147
+ position: centerPos
3148
+ },
3149
+ {
3150
+ type: TabStopType.RIGHT,
3151
+ position: rightPos
3152
+ }
3153
+ ],
3154
+ border: resolved.footer.divider ? {
3155
+ top: {
3156
+ style: BorderStyle.SINGLE,
3157
+ size: 4,
3158
+ space: 6,
3159
+ color: (resolved.footer.dividerColor || "#CBD5E1").replace("#", "")
3160
+ }
3161
+ } : void 0,
3162
+ children: footerRuns,
3163
+ spacing: { before: 120 }
2276
3164
  })
2277
3165
  ]
2278
3166
  }) : void 0;
2279
- const topMargin = parseMarginToTwip(((_d = metadata.margins) == null ? void 0 : _d.top) || ((_e = config.margins) == null ? void 0 : _e.top), 1440);
2280
- const bottomMargin = parseMarginToTwip(((_f = metadata.margins) == null ? void 0 : _f.bottom) || ((_g = config.margins) == null ? void 0 : _g.bottom), 1440);
2281
- const leftMargin = parseMarginToTwip(((_h = metadata.margins) == null ? void 0 : _h.left) || ((_i = config.margins) == null ? void 0 : _i.left), 1440);
2282
- const rightMargin = parseMarginToTwip(((_j = metadata.margins) == null ? void 0 : _j.right) || ((_k = config.margins) == null ? void 0 : _k.right), 1440);
2283
- const isLandscape = (metadata.orientation || config.orientation) === "landscape";
3167
+ const isLandscape = resolved.orientation === "landscape";
2284
3168
  const document = new Document({
2285
3169
  styles: {
2286
3170
  default: {
2287
3171
  document: {
2288
3172
  run: {
2289
- font: "Segoe UI",
2290
- size: 22,
2291
- // 11pt
2292
- color: "0F172A"
3173
+ font: defaultFont,
3174
+ size: 21,
3175
+ // 10.5pt
3176
+ color: textHex
3177
+ },
3178
+ paragraph: {
3179
+ spacing: {
3180
+ line: 276,
3181
+ // 1.15 line spacing
3182
+ after: 140
3183
+ }
2293
3184
  }
2294
3185
  }
2295
3186
  }
@@ -2299,13 +3190,15 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2299
3190
  properties: {
2300
3191
  page: {
2301
3192
  size: {
3193
+ width: resolved.paperDimensions.widthTwip,
3194
+ height: resolved.paperDimensions.heightTwip,
2302
3195
  orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
2303
3196
  },
2304
3197
  margin: {
2305
- top: topMargin,
2306
- bottom: bottomMargin,
2307
- left: leftMargin,
2308
- right: rightMargin,
3198
+ top: resolved.margins.topTwip,
3199
+ bottom: resolved.margins.bottomTwip,
3200
+ left: resolved.margins.leftTwip,
3201
+ right: resolved.margins.rightTwip,
2309
3202
  header: 720,
2310
3203
  footer: 720
2311
3204
  }
@@ -2319,6 +3212,140 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2319
3212
  });
2320
3213
  return await Packer.toBuffer(document);
2321
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
+ }
2322
3349
 
2323
3350
  // src/core/engine.ts
2324
3351
  async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgress) {
@@ -2331,15 +3358,15 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2331
3358
  if (fs5.existsSync(inputFilePathOrContent)) {
2332
3359
  isFilePath = true;
2333
3360
  rawMarkdown = fs5.readFileSync(inputFilePathOrContent, "utf-8");
2334
- baseDir = path5.dirname(path5.resolve(inputFilePathOrContent));
2335
- inputFileName = path5.basename(inputFilePathOrContent);
3361
+ baseDir = path7.dirname(path7.resolve(inputFilePathOrContent));
3362
+ inputFileName = path7.basename(inputFilePathOrContent);
2336
3363
  } else {
2337
3364
  rawMarkdown = inputFilePathOrContent;
2338
3365
  }
2339
3366
  onProgress == null ? void 0 : onProgress(`Parsing markdown AST: ${inputFileName}...`);
2340
3367
  const parsedDoc = parseMarkdownDocument(rawMarkdown);
2341
3368
  const baseName = inputFileName.replace(/\.(md|mdx|markdown)$/i, "");
2342
- 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;
2343
3370
  if (!fs5.existsSync(outputDir)) {
2344
3371
  fs5.mkdirSync(outputDir, { recursive: true });
2345
3372
  }
@@ -2351,7 +3378,7 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2351
3378
  if (fmt === "docx") {
2352
3379
  onProgress == null ? void 0 : onProgress(`Generating DOCX document: ${baseName}.docx...`);
2353
3380
  const docxBuffer = await buildDocxDocument(parsedDoc, config, baseDir);
2354
- const docxPath = path5.join(outputDir, `${baseName}.docx`);
3381
+ const docxPath = path7.join(outputDir, `${baseName}.docx`);
2355
3382
  fs5.writeFileSync(docxPath, docxBuffer);
2356
3383
  generatedFiles.push({
2357
3384
  format: "docx",
@@ -2362,7 +3389,7 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2362
3389
  } else if (fmt === "html") {
2363
3390
  onProgress == null ? void 0 : onProgress(`Generating HTML document: ${baseName}.html...`);
2364
3391
  const htmlString = await buildHtmlDocument(parsedDoc, config, baseDir);
2365
- const htmlPath = path5.join(outputDir, `${baseName}.html`);
3392
+ const htmlPath = path7.join(outputDir, `${baseName}.html`);
2366
3393
  fs5.writeFileSync(htmlPath, htmlString, "utf-8");
2367
3394
  generatedFiles.push({
2368
3395
  format: "html",
@@ -2373,7 +3400,7 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
2373
3400
  } else if (fmt === "pdf") {
2374
3401
  onProgress == null ? void 0 : onProgress(`Generating PDF document: ${baseName}.pdf...`);
2375
3402
  const pdfBuffer = await buildPdfDocument(parsedDoc, config, baseDir);
2376
- const pdfPath = path5.join(outputDir, `${baseName}.pdf`);
3403
+ const pdfPath = path7.join(outputDir, `${baseName}.pdf`);
2377
3404
  fs5.writeFileSync(pdfPath, pdfBuffer);
2378
3405
  generatedFiles.push({
2379
3406
  format: "pdf",
@@ -2426,8 +3453,16 @@ var App = ({ inputFile, config, onComplete }) => {
2426
3453
  }
2427
3454
  run();
2428
3455
  }, [inputFile, config, onComplete]);
3456
+ const targetFormats = Array.isArray(config.to) ? config.to : config.to ? [String(config.to)] : ["docx", "pdf"];
2429
3457
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2430
- /* @__PURE__ */ jsx4(Header, { inputFile, theme: config.theme }),
3458
+ /* @__PURE__ */ jsx4(
3459
+ Header,
3460
+ {
3461
+ inputFile,
3462
+ theme: config.theme,
3463
+ targetFormats
3464
+ }
3465
+ ),
2431
3466
  /* @__PURE__ */ jsx4(LiveProgress, { status, isCompiling }),
2432
3467
  /* @__PURE__ */ jsx4(SummaryTable, { result })
2433
3468
  ] });