@masumdev/markforge 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -550,6 +550,51 @@ var SYNTAX_COLORS_LIGHT = {
550
550
  plain: "24292E"
551
551
  // Near-black — identifiers
552
552
  };
553
+ var SYNTAX_THEMES = {
554
+ "github-dark": SYNTAX_COLORS,
555
+ "dark": SYNTAX_COLORS,
556
+ "github-light": SYNTAX_COLORS_LIGHT,
557
+ "light": SYNTAX_COLORS_LIGHT,
558
+ "dracula": {
559
+ keyword: "FF79C6",
560
+ string: "F1FA8C",
561
+ comment: "6272A4",
562
+ number: "BD93F9",
563
+ boolean: "BD93F9",
564
+ function: "50FA7B",
565
+ type: "8BE9FD",
566
+ operator: "FF79C6",
567
+ punctuation: "F8F8F2",
568
+ plain: "F8F8F2"
569
+ },
570
+ "monokai": {
571
+ keyword: "F92672",
572
+ string: "E6DB74",
573
+ comment: "75715E",
574
+ number: "AE81FF",
575
+ boolean: "AE81FF",
576
+ function: "A6E22E",
577
+ type: "66D9EF",
578
+ operator: "F92672",
579
+ punctuation: "F8F8F2",
580
+ plain: "F8F8F2"
581
+ },
582
+ "nord": {
583
+ keyword: "81A1C1",
584
+ string: "A3BE8C",
585
+ comment: "616E88",
586
+ number: "B48EAD",
587
+ boolean: "81A1C1",
588
+ function: "88C0D0",
589
+ type: "8FBCBB",
590
+ operator: "81A1C1",
591
+ punctuation: "ECEFF4",
592
+ plain: "D8DEE9"
593
+ }
594
+ };
595
+ function getSyntaxPalette(themeName = "github-dark") {
596
+ return SYNTAX_THEMES[themeName.toLowerCase()] || SYNTAX_THEMES["github-dark"];
597
+ }
553
598
  var JS_KEYWORDS = /* @__PURE__ */ new Set([
554
599
  "import",
555
600
  "from",
@@ -693,9 +738,9 @@ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
693
738
  "foreign",
694
739
  "references"
695
740
  ]);
696
- function tokenizeCodeLine(line, lang = "", theme = "dark") {
741
+ function tokenizeCodeLine(line, lang = "", theme = "github-dark") {
697
742
  var _a;
698
- const COLORS = theme === "light" ? SYNTAX_COLORS_LIGHT : SYNTAX_COLORS;
743
+ const COLORS = getSyntaxPalette(theme);
699
744
  if (!line) {
700
745
  return [{ text: " ", type: "plain", colorHex: COLORS.plain }];
701
746
  }
@@ -778,6 +823,10 @@ function tokenizeCodeLine(line, lang = "", theme = "dark") {
778
823
  tokens.push({ text: word, type: "keyword", colorHex: COLORS.keyword, bold: true });
779
824
  continue;
780
825
  }
826
+ if (/^[A-Z][a-zA-Z0-9_$]*$/.test(word)) {
827
+ tokens.push({ text: word, type: "type", colorHex: COLORS.type });
828
+ continue;
829
+ }
781
830
  let nextNonWs = pos;
782
831
  while (nextNonWs < line.length && /\s/.test(line[nextNonWs])) {
783
832
  nextNonWs++;
@@ -786,10 +835,6 @@ function tokenizeCodeLine(line, lang = "", theme = "dark") {
786
835
  tokens.push({ text: word, type: "function", colorHex: COLORS.function });
787
836
  continue;
788
837
  }
789
- if (/^[A-Z][a-zA-Z0-9_$]*$/.test(word)) {
790
- tokens.push({ text: word, type: "type", colorHex: COLORS.type });
791
- continue;
792
- }
793
838
  tokens.push({ text: word, type: "plain", colorHex: COLORS.plain });
794
839
  continue;
795
840
  }
@@ -802,10 +847,10 @@ function tokenizeCodeLine(line, lang = "", theme = "dark") {
802
847
  }
803
848
  return tokens;
804
849
  }
805
- function highlightCodeToHtml(code, lang = "") {
850
+ function highlightCodeToHtml(code, lang = "", theme = "github-dark") {
806
851
  const lines = (code || "").split("\n");
807
852
  const htmlLines = lines.map((line) => {
808
- const tokens = tokenizeCodeLine(line, lang);
853
+ const tokens = tokenizeCodeLine(line, lang, theme);
809
854
  return tokens.map((t) => {
810
855
  const escaped = t.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
811
856
  if (t.type === "plain") return escaped;
@@ -817,21 +862,62 @@ function highlightCodeToHtml(code, lang = "") {
817
862
 
818
863
  // src/core/mermaid/mermaidRenderer.ts
819
864
  import { spawnSync as spawnSync2 } from "child_process";
820
- import * as fs4 from "fs";
865
+ import * as fs5 from "fs";
821
866
  import * as os2 from "os";
822
- import * as path4 from "path";
823
- import { pathToFileURL as pathToFileURL2 } from "url";
867
+ import * as path5 from "path";
868
+ import { pathToFileURL as pathToFileURL3 } from "url";
824
869
 
825
870
  // src/core/pdf/pdfBuilder.ts
826
- import * as fs3 from "fs";
827
- import * as path3 from "path";
871
+ import * as fs4 from "fs";
872
+ import * as path4 from "path";
828
873
  import * as os from "os";
829
- import { pathToFileURL } from "url";
874
+ import { pathToFileURL as pathToFileURL2 } from "url";
830
875
  import { spawnSync } from "child_process";
831
876
 
832
877
  // src/core/html/htmlBuilder.ts
833
- import * as fs2 from "fs";
834
- import * as path2 from "path";
878
+ import * as fs3 from "fs";
879
+ import * as path3 from "path";
880
+
881
+ // src/config/types.ts
882
+ var OutputFormat = /* @__PURE__ */ ((OutputFormat2) => {
883
+ OutputFormat2["DOCX"] = "docx";
884
+ OutputFormat2["PDF"] = "pdf";
885
+ OutputFormat2["HTML"] = "html";
886
+ OutputFormat2["PNG"] = "png";
887
+ return OutputFormat2;
888
+ })(OutputFormat || {});
889
+ var Theme = /* @__PURE__ */ ((Theme2) => {
890
+ Theme2["CORPORATE"] = "corporate";
891
+ return Theme2;
892
+ })(Theme || {});
893
+ var Orientation = /* @__PURE__ */ ((Orientation2) => {
894
+ Orientation2["PORTRAIT"] = "portrait";
895
+ Orientation2["LANDSCAPE"] = "landscape";
896
+ return Orientation2;
897
+ })(Orientation || {});
898
+ var PaperSizeEnum = /* @__PURE__ */ ((PaperSizeEnum2) => {
899
+ PaperSizeEnum2["A4"] = "A4";
900
+ PaperSizeEnum2["LETTER"] = "Letter";
901
+ PaperSizeEnum2["LEGAL"] = "Legal";
902
+ PaperSizeEnum2["A3"] = "A3";
903
+ PaperSizeEnum2["A5"] = "A5";
904
+ return PaperSizeEnum2;
905
+ })(PaperSizeEnum || {});
906
+ var SyntaxTheme = /* @__PURE__ */ ((SyntaxTheme2) => {
907
+ SyntaxTheme2["GITHUB_DARK"] = "github-dark";
908
+ SyntaxTheme2["GITHUB_LIGHT"] = "github-light";
909
+ SyntaxTheme2["DRACULA"] = "dracula";
910
+ SyntaxTheme2["MONOKAI"] = "monokai";
911
+ SyntaxTheme2["NORD"] = "nord";
912
+ return SyntaxTheme2;
913
+ })(SyntaxTheme || {});
914
+ var WatermarkPosition = /* @__PURE__ */ ((WatermarkPosition2) => {
915
+ WatermarkPosition2["DIAGONAL"] = "diagonal";
916
+ WatermarkPosition2["CENTER"] = "center";
917
+ WatermarkPosition2["TOP_RIGHT"] = "top-right";
918
+ WatermarkPosition2["BOTTOM_RIGHT"] = "bottom-right";
919
+ return WatermarkPosition2;
920
+ })(WatermarkPosition || {});
835
921
 
836
922
  // src/core/html/htmlThemes.ts
837
923
  var THEME_COMPONENTS = `
@@ -914,7 +1000,7 @@ hr { border: none; border-top: 1px solid var(--mf-border); margin: 2rem 0; }
914
1000
  pre { overflow: visible; white-space: pre-wrap; word-break: break-all; }
915
1001
  }
916
1002
  `;
917
- var THEME_DEFAULT = `
1003
+ var THEME_CORPORATE = `
918
1004
  :root {
919
1005
  --mf-bg: #ffffff;
920
1006
  --mf-text: #0f172a;
@@ -926,49 +1012,415 @@ var THEME_DEFAULT = `
926
1012
  --mf-card-bg: #f8fafc;
927
1013
  --mf-code-bg: #0f172a;
928
1014
  --mf-code-text: #f8fafc;
929
- --mf-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
1015
+ --mf-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
930
1016
  --mf-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
931
1017
  }
932
1018
  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; }
933
- .document-container { max-width: 860px; margin: 0 auto; }
1019
+ .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
934
1020
  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; }
935
- h1 { font-size: 2.2rem; border-bottom: 2px solid #33CDCF; padding-bottom: 0.5rem; }
936
- h2 { font-size: 1.6rem; color: #009DA0; border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
1021
+ h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1022
+ h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
937
1023
  h3 { font-size: 1.3rem; }
938
1024
  h4 { font-size: 1.1rem; }
939
1025
  p { margin: 0.8rem 0; }
940
1026
  `;
941
- var THEME_ACADEMIC = `
1027
+ var THEME_DEFAULT = THEME_CORPORATE;
1028
+ var THEMES = {
1029
+ corporate: THEME_CORPORATE,
1030
+ default: THEME_CORPORATE
1031
+ };
1032
+ function generateThemeCss(theme) {
1033
+ if (!theme || theme === "corporate" || theme === "default" || theme === "corporate" /* CORPORATE */) {
1034
+ return THEME_CORPORATE;
1035
+ }
1036
+ if (typeof theme === "object") {
1037
+ const bg = theme.backgroundColor || "#ffffff";
1038
+ const text = theme.textColor || "#0f172a";
1039
+ const textMuted = theme.textMuted || "#64748b";
1040
+ const primary = theme.primaryColor || "#33CDCF";
1041
+ const primaryDark = theme.primaryDark || primary;
1042
+ const primaryLight = theme.primaryLight || "#ECFDFD";
1043
+ const border = theme.borderColor || "#e2e8f0";
1044
+ const cardBg = theme.cardBackground || "#f8fafc";
1045
+ const codeBg = theme.codeBackground || "#0f172a";
1046
+ const codeText = theme.codeText || "#f8fafc";
1047
+ const font = theme.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
1048
+ const fontMono = theme.fontMono || "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
1049
+ return `
942
1050
  :root {
943
- --mf-bg: #ffffff;
944
- --mf-text: #1a1a1a;
945
- --mf-text-muted: #555;
946
- --mf-primary: #33CDCF;
947
- --mf-primary-dark: #009DA0;
948
- --mf-primary-light: #ECFDFD;
949
- --mf-border: #ccc;
950
- --mf-card-bg: #f9f9f9;
951
- --mf-code-bg: #1e1e1e;
952
- --mf-code-text: #d4d4d4;
953
- --mf-font-family: "Merriweather", "Georgia", "Times New Roman", serif;
954
- --mf-font-mono: "Courier New", Courier, monospace;
1051
+ --mf-bg: ${bg};
1052
+ --mf-text: ${text};
1053
+ --mf-text-muted: ${textMuted};
1054
+ --mf-primary: ${primary};
1055
+ --mf-primary-dark: ${primaryDark};
1056
+ --mf-primary-light: ${primaryLight};
1057
+ --mf-border: ${border};
1058
+ --mf-card-bg: ${cardBg};
1059
+ --mf-code-bg: ${codeBg};
1060
+ --mf-code-text: ${codeText};
1061
+ --mf-font-family: ${font};
1062
+ --mf-font-mono: ${fontMono};
955
1063
  }
956
- body { font-family: var(--mf-font-family); font-size: 16px; line-height: 1.8; padding: 3rem; color: var(--mf-text); }
957
- .document-container { max-width: 780px; margin: 0 auto; text-align: justify; }
958
- h1, h2, h3 { font-family: "Times New Roman", Times, serif; font-weight: bold; text-align: left; }
959
- h1 { font-size: 2rem; border-bottom: 1px solid #000; padding-bottom: 0.3rem; }
960
- h2 { font-size: 1.4rem; border-bottom: 1px solid #ccc; padding-bottom: 0.2rem; }
961
- h3 { font-size: 1.2rem; }
962
- p { margin: 0.9rem 0; }
1064
+ 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; }
1065
+ .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1066
+ 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; }
1067
+ h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1068
+ h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid var(--mf-border); padding-bottom: 0.4rem; }
1069
+ h3 { font-size: 1.3rem; }
1070
+ h4 { font-size: 1.1rem; }
1071
+ p { margin: 0.8rem 0; }
1072
+ ${theme.customCss || ""}
963
1073
  `;
964
- var THEMES = {
965
- default: THEME_DEFAULT,
966
- academic: THEME_ACADEMIC,
967
- github: THEME_DEFAULT,
968
- corporate: THEME_DEFAULT,
969
- minimal: THEME_DEFAULT,
970
- dracula: THEME_DEFAULT
1074
+ }
1075
+ if (typeof theme === "string" && THEMES[theme]) {
1076
+ return THEMES[theme];
1077
+ }
1078
+ return THEME_CORPORATE;
1079
+ }
1080
+
1081
+ // src/config/loadConfig.ts
1082
+ import * as fs2 from "fs";
1083
+ import * as path2 from "path";
1084
+ import { pathToFileURL } from "url";
1085
+ import * as YAML from "yaml";
1086
+ var DEFAULT_CONFIG_FILENAMES = [
1087
+ "markforge.config.json",
1088
+ ".markforgerc.json",
1089
+ "markforge.config.yaml",
1090
+ "markforge.config.yml",
1091
+ ".markforgerc.yaml",
1092
+ ".markforgerc.yml",
1093
+ ".markforgerc",
1094
+ "markforge.config.ts",
1095
+ "markforge.config.js",
1096
+ "markforge.config.mjs",
1097
+ "markforge.config.cjs"
1098
+ ];
1099
+ var DEFAULT_CONFIG = {
1100
+ to: ["docx", "pdf"],
1101
+ outputDir: void 0,
1102
+ theme: "default",
1103
+ css: void 0,
1104
+ orientation: "portrait",
1105
+ paperSize: "A4",
1106
+ margins: {
1107
+ top: "2.5cm",
1108
+ bottom: "2.5cm",
1109
+ left: "2.5cm",
1110
+ right: "2.5cm"
1111
+ },
1112
+ header: void 0,
1113
+ footer: {
1114
+ right: "Page {page} of {pages}"
1115
+ },
1116
+ toc: false,
1117
+ watermark: void 0,
1118
+ embedImages: true,
1119
+ metadata: void 0,
1120
+ watch: false,
1121
+ serve: false,
1122
+ port: 4e3,
1123
+ open: false,
1124
+ bundleHtml: true,
1125
+ syntaxTheme: "github-dark"
1126
+ };
1127
+ function discoverConfigFile(startDir) {
1128
+ const dirsToCheck = [];
1129
+ let curr = path2.resolve(startDir);
1130
+ while (curr) {
1131
+ dirsToCheck.push(curr);
1132
+ const parent = path2.dirname(curr);
1133
+ if (parent === curr) break;
1134
+ curr = parent;
1135
+ }
1136
+ const cwd = path2.resolve(process.cwd());
1137
+ if (!dirsToCheck.includes(cwd)) {
1138
+ dirsToCheck.push(cwd);
1139
+ }
1140
+ for (const dir of dirsToCheck) {
1141
+ for (const filename of DEFAULT_CONFIG_FILENAMES) {
1142
+ const candidate = path2.join(dir, filename);
1143
+ if (fs2.existsSync(candidate) && fs2.statSync(candidate).isFile()) {
1144
+ return candidate;
1145
+ }
1146
+ }
1147
+ }
1148
+ return null;
1149
+ }
1150
+ async function loadConfig(customPath, startDir = process.cwd()) {
1151
+ let resolvedPath = null;
1152
+ if (customPath) {
1153
+ resolvedPath = path2.isAbsolute(customPath) ? customPath : path2.resolve(process.cwd(), customPath);
1154
+ if (!fs2.existsSync(resolvedPath)) {
1155
+ const altPath = path2.resolve(startDir, customPath);
1156
+ if (fs2.existsSync(altPath)) {
1157
+ resolvedPath = altPath;
1158
+ } else {
1159
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
1160
+ }
1161
+ }
1162
+ } else {
1163
+ resolvedPath = discoverConfigFile(startDir);
1164
+ }
1165
+ if (!resolvedPath) {
1166
+ return {
1167
+ config: { ...DEFAULT_CONFIG },
1168
+ configPath: null
1169
+ };
1170
+ }
1171
+ const ext = path2.extname(resolvedPath).toLowerCase();
1172
+ let userConfig = {};
1173
+ try {
1174
+ if (ext === ".json" || ext === "" || resolvedPath.endsWith(".markforgerc")) {
1175
+ const raw = fs2.readFileSync(resolvedPath, "utf-8").trim();
1176
+ try {
1177
+ userConfig = JSON.parse(raw);
1178
+ } catch {
1179
+ userConfig = YAML.parse(raw) || {};
1180
+ }
1181
+ } else if (ext === ".yaml" || ext === ".yml") {
1182
+ const raw = fs2.readFileSync(resolvedPath, "utf-8");
1183
+ userConfig = YAML.parse(raw) || {};
1184
+ } else if (ext === ".ts" || ext === ".js" || ext === ".mjs" || ext === ".cjs") {
1185
+ try {
1186
+ const fileUrl = `${pathToFileURL(resolvedPath).href}?t=${Date.now()}`;
1187
+ const mod = await import(fileUrl);
1188
+ const rawExport = mod.default ?? mod.config ?? mod;
1189
+ userConfig = (typeof rawExport === "function" ? await rawExport() : rawExport) || {};
1190
+ } catch (importErr) {
1191
+ try {
1192
+ const mod = __require(resolvedPath);
1193
+ const rawExport = mod.default ?? mod.config ?? mod;
1194
+ userConfig = (typeof rawExport === "function" ? await rawExport() : rawExport) || {};
1195
+ } catch {
1196
+ throw importErr;
1197
+ }
1198
+ }
1199
+ }
1200
+ } catch (err) {
1201
+ throw new Error(
1202
+ `Failed to parse configuration file at ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`
1203
+ );
1204
+ }
1205
+ if (userConfig && typeof userConfig === "object" && "$schema" in userConfig) {
1206
+ delete userConfig.$schema;
1207
+ }
1208
+ const parsedConfig = userConfig;
1209
+ const mergedConfig = {
1210
+ ...DEFAULT_CONFIG,
1211
+ ...parsedConfig,
1212
+ margins: {
1213
+ ...DEFAULT_CONFIG.margins,
1214
+ ...parsedConfig.margins || {}
1215
+ },
1216
+ header: parsedConfig.header !== void 0 ? parsedConfig.header : DEFAULT_CONFIG.header,
1217
+ footer: parsedConfig.footer !== void 0 ? parsedConfig.footer : DEFAULT_CONFIG.footer,
1218
+ metadata: {
1219
+ ...DEFAULT_CONFIG.metadata || {},
1220
+ ...parsedConfig.metadata || {}
1221
+ }
1222
+ };
1223
+ return {
1224
+ config: mergedConfig,
1225
+ configPath: resolvedPath
1226
+ };
1227
+ }
1228
+
1229
+ // src/config/resolveConfig.ts
1230
+ var PAPER_DIMENSIONS_TWIP = {
1231
+ A4: { width: 11906, height: 16838 },
1232
+ // 210mm x 297mm
1233
+ Letter: { width: 12240, height: 15840 },
1234
+ // 8.5in x 11in
1235
+ Legal: { width: 12240, height: 20160 },
1236
+ // 8.5in x 14in
1237
+ A3: { width: 16838, height: 23811 },
1238
+ // 297mm x 420mm
1239
+ A5: { width: 8390, height: 11906 }
1240
+ // 148mm x 210mm
971
1241
  };
1242
+ function parseMarginToTwip(margin, defaultTwip = 1440) {
1243
+ if (typeof margin === "number") return margin;
1244
+ if (!margin) return defaultTwip;
1245
+ const str = margin.trim().toLowerCase();
1246
+ if (str.endsWith("cm")) {
1247
+ const cm = parseFloat(str);
1248
+ return isNaN(cm) ? defaultTwip : Math.round(cm * 566.929);
1249
+ }
1250
+ if (str.endsWith("mm")) {
1251
+ const mm = parseFloat(str);
1252
+ return isNaN(mm) ? defaultTwip : Math.round(mm * 56.6929);
1253
+ }
1254
+ if (str.endsWith("in") || str.endsWith("inch")) {
1255
+ const inch = parseFloat(str);
1256
+ return isNaN(inch) ? defaultTwip : Math.round(inch * 1440);
1257
+ }
1258
+ if (str.endsWith("pt")) {
1259
+ const pt = parseFloat(str);
1260
+ return isNaN(pt) ? defaultTwip : Math.round(pt * 20);
1261
+ }
1262
+ const val = parseFloat(str);
1263
+ return isNaN(val) ? defaultTwip : Math.round(val);
1264
+ }
1265
+ function formatMarginCss(margin, defaultCss = "2.5cm") {
1266
+ if (margin === void 0 || margin === null) return defaultCss;
1267
+ if (typeof margin === "number") return `${margin}pt`;
1268
+ const str = margin.trim();
1269
+ if (!str) return defaultCss;
1270
+ if (/^[0-9.]+$/.test(str)) return `${str}pt`;
1271
+ return str;
1272
+ }
1273
+ function replaceDocumentTokens(template = "", meta) {
1274
+ return template.replace(/\{title\}/gi, meta.title || "").replace(/\{subtitle\}/gi, meta.subtitle || "").replace(/\{author\}/gi, meta.author || "").replace(/\{version\}/gi, meta.version || "").replace(/\{date\}/gi, meta.date || "").replace(/\{company\}/gi, meta.company || "");
1275
+ }
1276
+ function normalizeWatermark(rawWatermark) {
1277
+ if (!rawWatermark) {
1278
+ return void 0;
1279
+ }
1280
+ if (typeof rawWatermark === "string") {
1281
+ const text = rawWatermark.trim();
1282
+ if (!text) return void 0;
1283
+ return {
1284
+ text,
1285
+ color: "#94a3b8",
1286
+ opacity: 0.08,
1287
+ fontSize: 54,
1288
+ rotate: -45,
1289
+ position: "diagonal"
1290
+ };
1291
+ }
1292
+ if (typeof rawWatermark === "object") {
1293
+ if (!rawWatermark.text || !rawWatermark.text.trim()) return void 0;
1294
+ return {
1295
+ text: rawWatermark.text.trim(),
1296
+ color: rawWatermark.color || "#94a3b8",
1297
+ opacity: typeof rawWatermark.opacity === "number" ? rawWatermark.opacity : 0.08,
1298
+ fontSize: rawWatermark.fontSize || 54,
1299
+ rotate: typeof rawWatermark.rotate === "number" ? rawWatermark.rotate : -45,
1300
+ position: rawWatermark.position || "diagonal"
1301
+ };
1302
+ }
1303
+ return void 0;
1304
+ }
1305
+ function normalizeHeaderFooterSlot(rawSlot, parent, meta) {
1306
+ if (!rawSlot) return void 0;
1307
+ if (typeof rawSlot === "string") {
1308
+ const text = replaceDocumentTokens(rawSlot, meta).trim();
1309
+ if (!text) return void 0;
1310
+ return {
1311
+ text,
1312
+ color: (parent == null ? void 0 : parent.color) || "#94A3B8",
1313
+ fontSize: (parent == null ? void 0 : parent.size) || 9,
1314
+ fontFamily: (parent == null ? void 0 : parent.font) || "Segoe UI",
1315
+ bold: false,
1316
+ italic: false
1317
+ };
1318
+ }
1319
+ if (typeof rawSlot === "object") {
1320
+ const text = replaceDocumentTokens(rawSlot.text || "", meta).trim();
1321
+ if (!text) return void 0;
1322
+ return {
1323
+ text,
1324
+ color: rawSlot.color || (parent == null ? void 0 : parent.color) || "#94A3B8",
1325
+ fontSize: rawSlot.fontSize || (parent == null ? void 0 : parent.size) || 9,
1326
+ fontFamily: rawSlot.fontFamily || (parent == null ? void 0 : parent.font) || "Segoe UI",
1327
+ bold: Boolean(rawSlot.bold),
1328
+ italic: Boolean(rawSlot.italic)
1329
+ };
1330
+ }
1331
+ return void 0;
1332
+ }
1333
+ function normalizeHeaderFooter(raw, meta) {
1334
+ if (!raw) return void 0;
1335
+ const left = normalizeHeaderFooterSlot(raw.left, raw, meta);
1336
+ const center = normalizeHeaderFooterSlot(raw.center, raw, meta);
1337
+ const right = normalizeHeaderFooterSlot(raw.right, raw, meta);
1338
+ if (!left && !center && !right) return void 0;
1339
+ return {
1340
+ left,
1341
+ center,
1342
+ right,
1343
+ font: raw.font || "Segoe UI",
1344
+ size: raw.size || 9,
1345
+ color: raw.color || "#94A3B8",
1346
+ divider: Boolean(raw.divider),
1347
+ dividerColor: raw.dividerColor || "#E2E8F0"
1348
+ };
1349
+ }
1350
+ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1351
+ const configMeta = userConfig.metadata || {};
1352
+ const mergedMeta = { ...configMeta, ...frontmatter };
1353
+ const title = mergedMeta.title || "MarkForge Document";
1354
+ const subtitle = mergedMeta.subtitle || void 0;
1355
+ const author = Array.isArray(mergedMeta.author) ? mergedMeta.author.join(", ") : mergedMeta.author || void 0;
1356
+ const date = mergedMeta.date || void 0;
1357
+ const version = mergedMeta.version || void 0;
1358
+ const company = mergedMeta.company || void 0;
1359
+ const lang = mergedMeta.lang || "en";
1360
+ const tokenContext = { title, subtitle, author, version, date, company };
1361
+ const theme = mergedMeta.theme || userConfig.theme || DEFAULT_CONFIG.theme;
1362
+ const orientation = mergedMeta.orientation || userConfig.orientation || DEFAULT_CONFIG.orientation;
1363
+ const paperSize = mergedMeta.paperSize || userConfig.paperSize || DEFAULT_CONFIG.paperSize;
1364
+ const baseDim = PAPER_DIMENSIONS_TWIP[paperSize] || PAPER_DIMENSIONS_TWIP.A4;
1365
+ const paperDimensions = orientation === "landscape" ? { widthTwip: baseDim.height, heightTwip: baseDim.width } : { widthTwip: baseDim.width, heightTwip: baseDim.height };
1366
+ const fmMargin = mergedMeta.margins || {};
1367
+ const cfgMargin = userConfig.margins || {};
1368
+ const defMargin = DEFAULT_CONFIG.margins || {};
1369
+ const topRaw = fmMargin.top ?? cfgMargin.top ?? defMargin.top ?? "2.5cm";
1370
+ const bottomRaw = fmMargin.bottom ?? cfgMargin.bottom ?? defMargin.bottom ?? "2.5cm";
1371
+ const leftRaw = fmMargin.left ?? cfgMargin.left ?? defMargin.left ?? "2.5cm";
1372
+ const rightRaw = fmMargin.right ?? cfgMargin.right ?? defMargin.right ?? "2.5cm";
1373
+ const margins = {
1374
+ top: formatMarginCss(topRaw),
1375
+ bottom: formatMarginCss(bottomRaw),
1376
+ left: formatMarginCss(leftRaw),
1377
+ right: formatMarginCss(rightRaw),
1378
+ topTwip: parseMarginToTwip(topRaw),
1379
+ bottomTwip: parseMarginToTwip(bottomRaw),
1380
+ leftTwip: parseMarginToTwip(leftRaw),
1381
+ rightTwip: parseMarginToTwip(rightRaw)
1382
+ };
1383
+ const rawHeader = mergedMeta.header || userConfig.header || DEFAULT_CONFIG.header;
1384
+ const rawFooter = mergedMeta.footer || userConfig.footer || DEFAULT_CONFIG.footer;
1385
+ const header = normalizeHeaderFooter(rawHeader, tokenContext);
1386
+ const footer = normalizeHeaderFooter(rawFooter, tokenContext);
1387
+ const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1388
+ const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1389
+ const watermark = normalizeWatermark(rawWatermark);
1390
+ const cssList = [];
1391
+ const addCss = (item) => {
1392
+ if (!item) return;
1393
+ if (Array.isArray(item)) cssList.push(...item);
1394
+ else cssList.push(item);
1395
+ };
1396
+ addCss(userConfig.css);
1397
+ addCss(mergedMeta.css);
1398
+ const embedImages = typeof userConfig.embedImages === "boolean" ? userConfig.embedImages : DEFAULT_CONFIG.embedImages;
1399
+ const bundleHtml = typeof userConfig.bundleHtml === "boolean" ? userConfig.bundleHtml : DEFAULT_CONFIG.bundleHtml;
1400
+ const syntaxTheme = userConfig.syntaxTheme || DEFAULT_CONFIG.syntaxTheme || "github-dark";
1401
+ return {
1402
+ title,
1403
+ subtitle,
1404
+ author,
1405
+ date,
1406
+ version,
1407
+ company,
1408
+ lang,
1409
+ theme,
1410
+ orientation,
1411
+ paperSize,
1412
+ paperDimensions,
1413
+ margins,
1414
+ header,
1415
+ footer,
1416
+ toc,
1417
+ watermark,
1418
+ css: cssList,
1419
+ embedImages,
1420
+ bundleHtml,
1421
+ syntaxTheme
1422
+ };
1423
+ }
972
1424
 
973
1425
  // src/core/html/htmlBuilder.ts
974
1426
  function escapeHtml(str) {
@@ -1021,43 +1473,41 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1021
1473
  return result;
1022
1474
  }
1023
1475
  async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1024
- var _a;
1025
- const metadata = { ...config.metadata, ...doc.metadata };
1026
- const themeName = metadata.theme || config.theme || "default";
1027
- const baseThemeCss = THEMES[themeName] || THEMES.default;
1476
+ const resolved = resolveDocumentConfig(doc.metadata, config);
1477
+ const baseThemeCss = generateThemeCss(resolved.theme);
1028
1478
  let customCss = "";
1029
- if (config.css) {
1030
- const cssList = Array.isArray(config.css) ? config.css : [config.css];
1031
- for (const cssPath of cssList) {
1032
- const fullCssPath = path2.isAbsolute(cssPath) ? cssPath : path2.resolve(baseDir, cssPath);
1033
- if (fs2.existsSync(fullCssPath)) {
1034
- customCss += `
1479
+ for (const cssPath of resolved.css) {
1480
+ const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
1481
+ if (fs3.existsSync(fullCssPath)) {
1482
+ customCss += `
1035
1483
  /* Custom CSS: ${cssPath} */
1036
- ` + fs2.readFileSync(fullCssPath, "utf-8");
1037
- }
1484
+ ` + fs3.readFileSync(fullCssPath, "utf-8");
1038
1485
  }
1039
1486
  }
1040
1487
  const inlinedCss = doc.inlinedStyles.join("\n");
1041
1488
  let bodyHtml = "";
1042
- if (metadata.title) {
1489
+ if (resolved.title) {
1043
1490
  bodyHtml += ` <header class="document-header">
1044
1491
  `;
1045
- bodyHtml += ` <h1 class="document-title">${escapeHtml(metadata.title)}</h1>
1492
+ bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1046
1493
  `;
1047
- if (metadata.subtitle) {
1048
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(metadata.subtitle)}</div>
1494
+ if (resolved.subtitle) {
1495
+ bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1049
1496
  `;
1050
1497
  }
1051
- if (metadata.author || metadata.date) {
1498
+ if (resolved.author || resolved.date || resolved.version) {
1052
1499
  bodyHtml += ` <div class="document-meta">
1053
1500
  `;
1054
- if (metadata.author) {
1055
- const authors = Array.isArray(metadata.author) ? metadata.author.join(", ") : metadata.author;
1056
- bodyHtml += ` <span>Author: ${escapeHtml(authors)}</span>
1501
+ if (resolved.author) {
1502
+ bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1503
+ `;
1504
+ }
1505
+ if (resolved.version) {
1506
+ bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1057
1507
  `;
1058
1508
  }
1059
- if (metadata.date) {
1060
- bodyHtml += ` <span>Date: ${escapeHtml(metadata.date)}</span>
1509
+ if (resolved.date) {
1510
+ bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1061
1511
  `;
1062
1512
  }
1063
1513
  bodyHtml += ` </div>
@@ -1066,22 +1516,20 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1066
1516
  bodyHtml += ` </header>
1067
1517
  `;
1068
1518
  }
1069
- if (config.toc || metadata.toc) {
1070
- if (doc.tocEntries.length > 0) {
1071
- bodyHtml += ` <nav class="table-of-contents">
1519
+ if (resolved.toc && doc.tocEntries.length > 0) {
1520
+ bodyHtml += ` <nav class="table-of-contents">
1072
1521
  `;
1073
- bodyHtml += ` <h2>Table of Contents</h2>
1522
+ bodyHtml += ` <h2>Table of Contents</h2>
1074
1523
  <ul>
1075
1524
  `;
1076
- for (const entry of doc.tocEntries) {
1077
- const indent = " ".repeat(entry.level);
1078
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1525
+ for (const entry of doc.tocEntries) {
1526
+ const indent = " ".repeat(entry.level);
1527
+ bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1079
1528
  `;
1080
- }
1081
- bodyHtml += ` </ul>
1529
+ }
1530
+ bodyHtml += ` </ul>
1082
1531
  </nav>
1083
1532
  `;
1084
- }
1085
1533
  }
1086
1534
  for (const node of doc.nodes) {
1087
1535
  if (node.type === "heading") {
@@ -1099,7 +1547,7 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1099
1547
  if (node.type === "codeBlock") {
1100
1548
  const lang = node.language || "";
1101
1549
  const langClass = lang ? ` class="language-${escapeHtml(lang)}"` : "";
1102
- const highlighted = highlightCodeToHtml(node.text || "", lang);
1550
+ const highlighted = highlightCodeToHtml(node.text || "", lang, resolved.syntaxTheme);
1103
1551
  bodyHtml += ` <pre><code${langClass}>${highlighted}</code></pre>
1104
1552
  `;
1105
1553
  continue;
@@ -1143,15 +1591,12 @@ ${escapeHtml(node.text || "")}
1143
1591
  for (const row of node.children) {
1144
1592
  bodyHtml += ` <tr>
1145
1593
  `;
1146
- if (row.children) {
1147
- for (let colIdx = 0; colIdx < row.children.length; colIdx++) {
1148
- const cell = row.children[colIdx];
1149
- const tag = row.isHeader ? "th" : "td";
1150
- const align = ((_a = node.align) == null ? void 0 : _a[colIdx]) ? ` style="text-align: ${node.align[colIdx]}"` : "";
1151
- const cellInner = await renderInlinesToHtml(cell.inlines, baseDir);
1152
- bodyHtml += ` <${tag}${align}>${cellInner}</${tag}>
1594
+ for (const cell of row.children || []) {
1595
+ const tag = cell.isHeader ? "th" : "td";
1596
+ const align = cell.align ? ` align="${cell.align}"` : "";
1597
+ const inner = await renderInlinesToHtml(cell.inlines, baseDir);
1598
+ bodyHtml += ` <${tag}${align}>${inner}</${tag}>
1153
1599
  `;
1154
- }
1155
1600
  }
1156
1601
  bodyHtml += ` </tr>
1157
1602
  `;
@@ -1165,57 +1610,52 @@ ${escapeHtml(node.text || "")}
1165
1610
  bodyHtml += ` <${tag}>
1166
1611
  `;
1167
1612
  for (const item of node.children) {
1168
- const itemInner = await renderInlinesToHtml(item.inlines, baseDir);
1169
- let prefix = "";
1170
- if (item.checked !== void 0) {
1171
- prefix = `<input type="checkbox" disabled ${item.checked ? "checked" : ""}/> `;
1172
- }
1173
- bodyHtml += ` <li>${prefix}${itemInner}</li>
1613
+ const inner = await renderInlinesToHtml(item.inlines, baseDir);
1614
+ bodyHtml += ` <li>${inner}</li>
1174
1615
  `;
1175
1616
  }
1176
1617
  bodyHtml += ` </${tag}>
1177
1618
  `;
1178
1619
  continue;
1179
1620
  }
1180
- if (node.type === "htmlBlock") {
1181
- bodyHtml += ` ${node.rawHtml}
1621
+ if (node.type === "thematicBreak") {
1622
+ bodyHtml += ` <hr />
1182
1623
  `;
1183
1624
  continue;
1184
1625
  }
1185
- if (node.type === "thematicBreak") {
1186
- bodyHtml += ` <hr />
1626
+ if (node.type === "htmlBlock") {
1627
+ bodyHtml += ` ${node.text}
1187
1628
  `;
1188
1629
  continue;
1189
1630
  }
1190
1631
  }
1191
- const wmConfig = config.watermark ?? metadata.watermark;
1192
- let watermarkHtml = "";
1193
1632
  let watermarkCss = "";
1194
- if (wmConfig) {
1195
- const wmText = typeof wmConfig === "string" ? wmConfig : wmConfig.text;
1196
- const opacity = typeof wmConfig === "object" && wmConfig.opacity !== void 0 ? wmConfig.opacity : 0.12;
1197
- const rotate = typeof wmConfig === "object" && wmConfig.rotate !== void 0 ? wmConfig.rotate : -45;
1198
- const color = typeof wmConfig === "object" && wmConfig.color ? wmConfig.color : "#94A3B8";
1199
- const fontSize = typeof wmConfig === "object" && wmConfig.fontSize ? `${wmConfig.fontSize}pt` : "52pt";
1633
+ let watermarkHtml = "";
1634
+ if (resolved.watermark) {
1635
+ const wm = resolved.watermark;
1200
1636
  watermarkCss = `
1201
1637
  .document-watermark {
1202
1638
  position: fixed;
1203
1639
  top: 50%;
1204
1640
  left: 50%;
1205
- transform: translate(-50%, -50%) rotate(${rotate}deg);
1206
- font-size: ${fontSize};
1207
- font-weight: 800;
1208
- color: ${color};
1209
- opacity: ${opacity};
1641
+ transform: translate(-50%, -50%) rotate(${wm.rotate}deg);
1642
+ font-size: ${wm.fontSize}pt;
1643
+ font-weight: 900;
1644
+ color: ${wm.color};
1645
+ opacity: ${wm.opacity};
1210
1646
  pointer-events: none;
1647
+ z-index: 0;
1211
1648
  user-select: none;
1212
- z-index: 9999;
1213
1649
  text-transform: uppercase;
1214
1650
  letter-spacing: 0.15em;
1215
1651
  white-space: nowrap;
1216
1652
  }
1653
+ .document-container {
1654
+ position: relative;
1655
+ z-index: 1;
1656
+ }
1217
1657
  `;
1218
- watermarkHtml = ` <div class="document-watermark">${escapeHtml(wmText)}</div>
1658
+ watermarkHtml = ` <div class="document-watermark">${escapeHtml(wm.text)}</div>
1219
1659
  `;
1220
1660
  }
1221
1661
  const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
@@ -1234,13 +1674,12 @@ ${escapeHtml(node.text || "")}
1234
1674
  }
1235
1675
  });
1236
1676
  </script>` : "";
1237
- const documentTitle = metadata.title || "MarkForge Document";
1238
1677
  return `<!DOCTYPE html>
1239
- <html lang="${metadata.lang || "en"}">
1678
+ <html lang="${resolved.lang}">
1240
1679
  <head>
1241
1680
  <meta charset="UTF-8">
1242
1681
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1243
- <title>${escapeHtml(documentTitle)}</title>
1682
+ <title>${escapeHtml(resolved.title)}</title>
1244
1683
  <style>
1245
1684
  ${THEME_COMPONENTS}
1246
1685
  ${baseThemeCss}
@@ -1259,10 +1698,10 @@ ${bodyHtml} </div>
1259
1698
 
1260
1699
  // src/core/pdf/pdfBuilder.ts
1261
1700
  function findChromeExecutable() {
1262
- if (process.env.CHROME_PATH && fs3.existsSync(process.env.CHROME_PATH)) {
1701
+ if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1263
1702
  return process.env.CHROME_PATH;
1264
1703
  }
1265
- if (process.env.PUPPETEER_EXECUTABLE_PATH && fs3.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
1704
+ if (process.env.PUPPETEER_EXECUTABLE_PATH && fs4.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
1266
1705
  return process.env.PUPPETEER_EXECUTABLE_PATH;
1267
1706
  }
1268
1707
  const isWin = process.platform === "win32";
@@ -1284,14 +1723,14 @@ function findChromeExecutable() {
1284
1723
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
1285
1724
  "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
1286
1725
  "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
1726
+ // Windows — Microsoft Edge (Native Windows 10/11 browser, enterprise whitelist friendly)
1727
+ `${winProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
1728
+ `${winProgramFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
1729
+ `${winLocalAppData}\\Microsoft\\Edge\\Application\\msedge.exe`,
1287
1730
  // Windows — Google Chrome
1288
1731
  `${winProgramFiles}\\Google\\Chrome\\Application\\chrome.exe`,
1289
1732
  `${winProgramFilesX86}\\Google\\Chrome\\Application\\chrome.exe`,
1290
1733
  `${winLocalAppData}\\Google\\Chrome\\Application\\chrome.exe`,
1291
- // Windows — Microsoft Edge (ships with Windows 10/11)
1292
- `${winProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
1293
- `${winProgramFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
1294
- `${winLocalAppData}\\Microsoft\\Edge\\Application\\msedge.exe`,
1295
1734
  // Windows — Brave Browser
1296
1735
  `${winProgramFiles}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
1297
1736
  `${winProgramFilesX86}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
@@ -1301,7 +1740,7 @@ function findChromeExecutable() {
1301
1740
  ].filter(Boolean);
1302
1741
  for (const candidate of candidates) {
1303
1742
  try {
1304
- if (fs3.existsSync(candidate)) {
1743
+ if (fs4.existsSync(candidate)) {
1305
1744
  return candidate;
1306
1745
  }
1307
1746
  } catch {
@@ -1314,7 +1753,7 @@ function findChromeExecutable() {
1314
1753
  const res = spawnSync(cmd, [name], { encoding: "utf-8" });
1315
1754
  if (res.status === 0 && res.stdout.trim()) {
1316
1755
  const binPath = res.stdout.split(/\r?\n/)[0].trim();
1317
- if (fs3.existsSync(binPath)) return binPath;
1756
+ if (fs4.existsSync(binPath)) return binPath;
1318
1757
  }
1319
1758
  }
1320
1759
  } catch {
@@ -1322,38 +1761,63 @@ function findChromeExecutable() {
1322
1761
  return null;
1323
1762
  }
1324
1763
  function injectPagedMediaStyles(html, config, metadata) {
1325
- var _a, _b, _c, _d;
1326
- const merged = { ...config.metadata, ...metadata };
1327
- const orientation = merged.orientation || config.orientation || "portrait";
1328
- const size = merged.paperSize || config.paperSize || "A4";
1329
- const margins = merged.margins || config.margins || {};
1330
- const top = margins.top || ((_a = config.margins) == null ? void 0 : _a.top) || "2.5cm";
1331
- const bottom = margins.bottom || ((_b = config.margins) == null ? void 0 : _b.bottom) || "2.5cm";
1332
- const left = margins.left || ((_c = config.margins) == null ? void 0 : _c.left) || "2.5cm";
1333
- const right = margins.right || ((_d = config.margins) == null ? void 0 : _d.right) || "2.5cm";
1334
- const headerCfg = merged.header;
1335
- const footerCfg = merged.footer;
1336
- const headerLeft = (headerCfg == null ? void 0 : headerCfg.left) ?? "";
1337
- const headerCenter = (headerCfg == null ? void 0 : headerCfg.center) ?? "";
1338
- const headerRight = (headerCfg == null ? void 0 : headerCfg.right) ?? "";
1339
- const footerLeft = ((footerCfg == null ? void 0 : footerCfg.left) ?? "").replace("{page}", "").replace("{pages}", "").trim();
1340
- const esc = (s) => s.replace(/"/g, '"').replace(/\\/g, "\\\\");
1341
- const pagedCss = `
1342
- @page {
1343
- size: ${size} ${orientation};
1764
+ var _a, _b, _c, _d, _e, _f;
1765
+ const resolved = resolveDocumentConfig(metadata || {}, config);
1766
+ const size = resolved.paperSize;
1767
+ const orientation = resolved.orientation;
1768
+ const top = resolved.margins.top;
1769
+ const bottom = resolved.margins.bottom;
1770
+ const left = resolved.margins.left;
1771
+ const right = resolved.margins.right;
1772
+ const esc = (s) => s.replace(/"/g, '\\"').replace(/\\/g, "\\\\");
1773
+ const buildZoneCss = (pos, zone, isPageCounter = false) => {
1774
+ if (!zone && !isPageCounter) return "";
1775
+ const color = (zone == null ? void 0 : zone.color) || "#94a3b8";
1776
+ const fontSize = (zone == null ? void 0 : zone.fontSize) ? `${zone.fontSize}pt` : "9pt";
1777
+ const fontFamily = (zone == null ? void 0 : zone.fontFamily) ? `font-family: ${zone.fontFamily};` : "";
1778
+ const fontWeight = (zone == null ? void 0 : zone.bold) ? "font-weight: bold;" : "";
1779
+ const fontStyle = (zone == null ? void 0 : zone.italic) ? "font-style: italic;" : "";
1780
+ let content = "";
1781
+ if (isPageCounter) {
1782
+ if ((zone == null ? void 0 : zone.text) && (zone.text.includes("{page}") || zone.text.includes("{pages}"))) {
1783
+ const parts = zone.text.split(/(\{page\}|\{pages\})/gi);
1784
+ const cssParts = parts.map((part) => {
1785
+ if (part.toLowerCase() === "{page}") return "counter(page)";
1786
+ if (part.toLowerCase() === "{pages}") return "counter(pages)";
1787
+ return `"${esc(part)}"`;
1788
+ });
1789
+ content = cssParts.join(" ");
1790
+ } else if (zone == null ? void 0 : zone.text) {
1791
+ content = `"${esc(zone.text)}"`;
1792
+ } else {
1793
+ content = `"Page " counter(page) " of " counter(pages)`;
1794
+ }
1795
+ } else if (zone == null ? void 0 : zone.text) {
1796
+ content = `"${esc(zone.text)}"`;
1797
+ }
1798
+ if (!content) return "";
1799
+ return `@${pos} {
1800
+ content: ${content};
1801
+ font-size: ${fontSize};
1802
+ color: ${color};
1803
+ ${fontFamily}
1804
+ ${fontWeight}
1805
+ ${fontStyle}
1806
+ }`;
1807
+ };
1808
+ const pagedCss = `
1809
+ @page {
1810
+ size: ${size} ${orientation};
1344
1811
  margin-top: ${top};
1345
1812
  margin-bottom: ${bottom};
1346
1813
  margin-left: ${left};
1347
1814
  margin-right: ${right};
1348
- ${headerLeft ? `@top-left { content: "${esc(headerLeft)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1349
- ${headerCenter ? `@top-center { content: "${esc(headerCenter)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1350
- ${headerRight ? `@top-right { content: "${esc(headerRight)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1351
- ${footerLeft ? `@bottom-left { content: "${esc(footerLeft)}"; font-size: 9pt; color: #94a3b8; }` : ""}
1352
- @bottom-right {
1353
- content: "Page " counter(page) " of " counter(pages);
1354
- font-size: 9pt;
1355
- color: #94a3b8;
1356
- }
1815
+ ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
1816
+ ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
1817
+ ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
1818
+ ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
1819
+ ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
1820
+ ${buildZoneCss("bottom-right", (_f = resolved.footer) == null ? void 0 : _f.right, true)}
1357
1821
  }
1358
1822
  @media print {
1359
1823
  body { padding: 0; }
@@ -1410,56 +1874,72 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
1410
1874
  if (chromePath) {
1411
1875
  const tmpId = Math.random().toString(36).substring(2, 9);
1412
1876
  const tmpDir = os.tmpdir();
1413
- const tmpHtml = path3.join(tmpDir, `markforge_${tmpId}.html`);
1414
- const tmpPdf = path3.join(tmpDir, `markforge_${tmpId}.pdf`);
1877
+ const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
1878
+ const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
1879
+ const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
1880
+ const isolatedFlags = [
1881
+ `--user-data-dir=${tmpProfile}`,
1882
+ "--no-first-run",
1883
+ "--no-default-browser-check",
1884
+ "--disable-sync",
1885
+ "--disable-background-networking",
1886
+ "--disable-component-update",
1887
+ "--disable-default-apps",
1888
+ "--disable-extensions",
1889
+ "--disable-domain-reliability",
1890
+ "--disable-client-side-phishing-detection",
1891
+ "--disable-breakpad",
1892
+ "--disable-component-extensions-with-background-pages",
1893
+ "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
1894
+ "--password-store=basic",
1895
+ "--use-mock-keychain",
1896
+ "--mute-audio",
1897
+ "--no-service-autorun",
1898
+ "--disable-gpu",
1899
+ "--no-sandbox",
1900
+ "--disable-setuid-sandbox",
1901
+ "--allow-file-access-from-files",
1902
+ "--disable-web-security",
1903
+ "--force-color-profile=srgb",
1904
+ "--no-pdf-header-footer"
1905
+ ];
1415
1906
  try {
1416
- fs3.writeFileSync(tmpHtml, pagedHtml, "utf-8");
1417
- const fileUrl = pathToFileURL(tmpHtml).href;
1907
+ fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
1908
+ const fileUrl = pathToFileURL2(tmpHtml).href;
1418
1909
  let res = spawnSync(
1419
1910
  chromePath,
1420
1911
  [
1421
1912
  "--headless=new",
1422
- "--disable-gpu",
1423
- "--no-sandbox",
1424
- "--disable-setuid-sandbox",
1425
- "--allow-file-access-from-files",
1426
- "--disable-web-security",
1427
- "--force-color-profile=srgb",
1913
+ ...isolatedFlags,
1428
1914
  "--run-all-compositor-stages-before-draw",
1429
1915
  "--virtual-time-budget=8000",
1430
- "--no-pdf-header-footer",
1431
1916
  `--print-to-pdf=${tmpPdf}`,
1432
1917
  fileUrl
1433
1918
  ],
1434
1919
  { timeout: 3e4 }
1435
1920
  );
1436
- if ((res.status !== 0 || !fs3.existsSync(tmpPdf)) && chromePath) {
1921
+ if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
1437
1922
  res = spawnSync(
1438
1923
  chromePath,
1439
1924
  [
1440
1925
  "--headless",
1441
- "--disable-gpu",
1442
- "--no-sandbox",
1443
- "--disable-setuid-sandbox",
1444
- "--allow-file-access-from-files",
1445
- "--disable-web-security",
1446
- "--force-color-profile=srgb",
1447
- "--no-pdf-header-footer",
1926
+ ...isolatedFlags,
1448
1927
  `--print-to-pdf=${tmpPdf}`,
1449
1928
  fileUrl
1450
1929
  ],
1451
1930
  { timeout: 3e4 }
1452
1931
  );
1453
1932
  }
1454
- if (fs3.existsSync(tmpPdf) && fs3.statSync(tmpPdf).size > 0) {
1455
- const pdfBuffer = fs3.readFileSync(tmpPdf);
1933
+ if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
1934
+ const pdfBuffer = fs4.readFileSync(tmpPdf);
1456
1935
  return pdfBuffer;
1457
1936
  }
1458
1937
  } catch {
1459
1938
  } finally {
1460
1939
  try {
1461
- if (fs3.existsSync(tmpHtml)) fs3.unlinkSync(tmpHtml);
1462
- if (fs3.existsSync(tmpPdf)) fs3.unlinkSync(tmpPdf);
1940
+ if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
1941
+ if (fs4.existsSync(tmpPdf)) fs4.unlinkSync(tmpPdf);
1942
+ if (fs4.existsSync(tmpProfile)) fs4.rmSync(tmpProfile, { recursive: true, force: true });
1463
1943
  } catch {
1464
1944
  }
1465
1945
  }
@@ -1475,8 +1955,8 @@ async function renderMermaidToPng(mermaidCode, _baseDir = process.cwd()) {
1475
1955
  }
1476
1956
  const tmpId = Math.random().toString(36).substring(2, 9);
1477
1957
  const tmpDir = os2.tmpdir();
1478
- const tmpHtml = path4.join(tmpDir, `mermaid_${tmpId}.html`);
1479
- const tmpScreenshot = path4.join(tmpDir, `mermaid_${tmpId}.png`);
1958
+ const tmpHtml = path5.join(tmpDir, `mermaid_${tmpId}.html`);
1959
+ const tmpScreenshot = path5.join(tmpDir, `mermaid_${tmpId}.png`);
1480
1960
  const htmlContent = `<!DOCTYPE html>
1481
1961
  <html>
1482
1962
  <head>
@@ -1515,8 +1995,8 @@ ${mermaidCode}
1515
1995
  </body>
1516
1996
  </html>`;
1517
1997
  try {
1518
- fs4.writeFileSync(tmpHtml, htmlContent, "utf-8");
1519
- const fileUrl = pathToFileURL2(tmpHtml).href;
1998
+ fs5.writeFileSync(tmpHtml, htmlContent, "utf-8");
1999
+ const fileUrl = pathToFileURL3(tmpHtml).href;
1520
2000
  const res = spawnSync2(
1521
2001
  chromePath,
1522
2002
  [
@@ -1534,15 +2014,15 @@ ${mermaidCode}
1534
2014
  ],
1535
2015
  { timeout: 15e3 }
1536
2016
  );
1537
- if (res.status === 0 && fs4.existsSync(tmpScreenshot)) {
1538
- const buffer = fs4.readFileSync(tmpScreenshot);
2017
+ if (res.status === 0 && fs5.existsSync(tmpScreenshot)) {
2018
+ const buffer = fs5.readFileSync(tmpScreenshot);
1539
2019
  return buffer;
1540
2020
  }
1541
2021
  } catch {
1542
2022
  } finally {
1543
2023
  try {
1544
- if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
1545
- if (fs4.existsSync(tmpScreenshot)) fs4.unlinkSync(tmpScreenshot);
2024
+ if (fs5.existsSync(tmpHtml)) fs5.unlinkSync(tmpHtml);
2025
+ if (fs5.existsSync(tmpScreenshot)) fs5.unlinkSync(tmpScreenshot);
1546
2026
  } catch {
1547
2027
  }
1548
2028
  }
@@ -1550,7 +2030,7 @@ ${mermaidCode}
1550
2030
  }
1551
2031
 
1552
2032
  // src/core/docx/docxBuilder.ts
1553
- function parseMarginToTwip(margin, defaultTwip = 1440) {
2033
+ function parseMarginToTwip2(margin, defaultTwip = 1440) {
1554
2034
  if (typeof margin === "number") return margin;
1555
2035
  if (!margin) return defaultTwip;
1556
2036
  const str = margin.trim().toLowerCase();
@@ -1573,7 +2053,7 @@ function parseMarginToTwip(margin, defaultTwip = 1440) {
1573
2053
  const val = parseFloat(str);
1574
2054
  return isNaN(val) ? defaultTwip : Math.round(val);
1575
2055
  }
1576
- async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
2056
+ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), options = {}) {
1577
2057
  const runs = [];
1578
2058
  for (const span of spans) {
1579
2059
  if (span.type === "image" && span.url) {
@@ -1603,7 +2083,10 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1603
2083
  new TextRun({
1604
2084
  text: span.content,
1605
2085
  style: "Hyperlink",
1606
- color: "0969DA",
2086
+ color: "009DA0",
2087
+ font: options.font,
2088
+ size: options.size,
2089
+ bold: options.bold,
1607
2090
  underline: {}
1608
2091
  })
1609
2092
  ],
@@ -1616,7 +2099,11 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1616
2099
  runs.push(
1617
2100
  new TextRun({
1618
2101
  text: span.content,
1619
- bold: true
2102
+ bold: true,
2103
+ font: options.font,
2104
+ size: options.size,
2105
+ color: options.color,
2106
+ italics: options.italics
1620
2107
  })
1621
2108
  );
1622
2109
  continue;
@@ -1625,7 +2112,11 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1625
2112
  runs.push(
1626
2113
  new TextRun({
1627
2114
  text: span.content,
1628
- italics: true
2115
+ italics: true,
2116
+ font: options.font,
2117
+ size: options.size,
2118
+ color: options.color,
2119
+ bold: options.bold
1629
2120
  })
1630
2121
  );
1631
2122
  continue;
@@ -1634,7 +2125,10 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1634
2125
  runs.push(
1635
2126
  new TextRun({
1636
2127
  text: span.content,
1637
- strike: true
2128
+ strike: true,
2129
+ font: options.font,
2130
+ size: options.size,
2131
+ color: options.color
1638
2132
  })
1639
2133
  );
1640
2134
  continue;
@@ -1642,22 +2136,23 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1642
2136
  if (span.type === "code") {
1643
2137
  runs.push(
1644
2138
  new TextRun({
1645
- text: ` ${span.content} `,
2139
+ text: span.content,
1646
2140
  font: "Consolas",
2141
+ size: options.size ? options.size - 2 : 19,
2142
+ color: "0F172A",
1647
2143
  shading: {
1648
2144
  type: ShadingType.CLEAR,
1649
- fill: "F1F5F9",
1650
- color: "0F172A"
2145
+ fill: "F1F5F9"
1651
2146
  }
1652
2147
  })
1653
2148
  );
1654
2149
  continue;
1655
2150
  }
1656
2151
  if (span.type === "htmlInline") {
1657
- let colorHex;
2152
+ let colorHex = options.color;
1658
2153
  let bgHex;
1659
- let isBold = false;
1660
- let isItalic = false;
2154
+ let isBold = !!options.bold;
2155
+ let isItalic = !!options.italics;
1661
2156
  if (span.style) {
1662
2157
  if (span.style.color) {
1663
2158
  colorHex = span.style.color.replace("#", "").trim();
@@ -1673,13 +2168,9 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1673
2168
  }
1674
2169
  }
1675
2170
  if (span.children && span.children.length > 0) {
1676
- const childRuns = await convertInlinesToTextRuns(span.children, baseDir);
2171
+ const childRuns = await convertInlinesToTextRuns(span.children, baseDir, options);
1677
2172
  for (const child of childRuns) {
1678
- if (child instanceof TextRun) {
1679
- runs.push(child);
1680
- } else {
1681
- runs.push(child);
1682
- }
2173
+ runs.push(child);
1683
2174
  }
1684
2175
  continue;
1685
2176
  }
@@ -1689,6 +2180,8 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1689
2180
  color: colorHex,
1690
2181
  bold: isBold,
1691
2182
  italics: isItalic,
2183
+ font: options.font,
2184
+ size: options.size,
1692
2185
  shading: bgHex ? { type: ShadingType.CLEAR, fill: bgHex } : void 0
1693
2186
  })
1694
2187
  );
@@ -1696,91 +2189,239 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd()) {
1696
2189
  }
1697
2190
  runs.push(
1698
2191
  new TextRun({
1699
- text: span.content
2192
+ text: span.content,
2193
+ font: options.font,
2194
+ size: options.size,
2195
+ color: options.color,
2196
+ bold: options.bold,
2197
+ italics: options.italics
1700
2198
  })
1701
2199
  );
1702
2200
  }
1703
2201
  return runs;
1704
2202
  }
1705
2203
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1706
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1707
- const metadata = { ...config.metadata, ...doc.metadata };
2204
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2205
+ const resolved = resolveDocumentConfig(doc.metadata, config);
1708
2206
  const docElements = [];
1709
- if (metadata.title) {
2207
+ const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
2208
+ const primaryHex = (themeProps.primaryColor || "#33CDCF").replace("#", "");
2209
+ const primaryDarkHex = (themeProps.primaryDark || "#009DA0").replace("#", "");
2210
+ const textHex = (themeProps.textColor || "#0F172A").replace("#", "");
2211
+ const textMutedHex = (themeProps.textMuted || "#64748B").replace("#", "");
2212
+ const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2213
+ const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2214
+ const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2215
+ if (resolved.title) {
1710
2216
  docElements.push(
1711
2217
  new Paragraph({
1712
- text: metadata.title,
1713
- heading: HeadingLevel.TITLE,
1714
- spacing: { before: 200, after: 120 }
2218
+ children: [
2219
+ new TextRun({
2220
+ text: resolved.title,
2221
+ bold: true,
2222
+ size: 44,
2223
+ // 22pt
2224
+ color: textHex,
2225
+ font: defaultFont
2226
+ })
2227
+ ],
2228
+ spacing: { before: 120, after: 80 }
1715
2229
  })
1716
2230
  );
1717
- if (metadata.subtitle) {
2231
+ if (resolved.subtitle) {
1718
2232
  docElements.push(
1719
2233
  new Paragraph({
1720
2234
  children: [
1721
2235
  new TextRun({
1722
- text: metadata.subtitle,
1723
- italics: true,
1724
- color: "64748B",
1725
- size: 24
2236
+ text: resolved.subtitle,
2237
+ color: textMutedHex,
2238
+ size: 24,
1726
2239
  // 12pt
2240
+ font: defaultFont
1727
2241
  })
1728
2242
  ],
1729
- spacing: { after: 180 }
2243
+ spacing: { before: 60, after: 120 }
1730
2244
  })
1731
2245
  );
1732
2246
  }
1733
- if (metadata.author || metadata.date) {
2247
+ if (resolved.author || resolved.date || resolved.version || resolved.company) {
1734
2248
  const metaParts = [];
1735
- if (metadata.author) metaParts.push(`Author: ${Array.isArray(metadata.author) ? metadata.author.join(", ") : metadata.author}`);
1736
- if (metadata.date) metaParts.push(`Date: ${metadata.date}`);
2249
+ if (resolved.author) metaParts.push(`Author: ${resolved.author}`);
2250
+ if (resolved.version) metaParts.push(`Version: ${resolved.version}`);
2251
+ if (resolved.date) metaParts.push(`Date: ${resolved.date}`);
1737
2252
  docElements.push(
1738
2253
  new Paragraph({
1739
2254
  children: [
1740
2255
  new TextRun({
1741
- text: metaParts.join(" | "),
1742
- color: "94A3B8",
1743
- size: 20
1744
- // 10pt
2256
+ text: metaParts.join(" "),
2257
+ color: textMutedHex,
2258
+ size: 18,
2259
+ // 9pt
2260
+ font: defaultFont
1745
2261
  })
1746
2262
  ],
1747
- spacing: { after: 360 },
2263
+ spacing: { before: 40, after: 240 },
1748
2264
  border: {
1749
2265
  bottom: {
1750
- color: "E2E8F0",
1751
- space: 10,
2266
+ color: borderHex,
2267
+ space: 12,
1752
2268
  style: BorderStyle.SINGLE,
1753
- size: 6
2269
+ size: 4
1754
2270
  }
1755
2271
  }
1756
2272
  })
1757
2273
  );
1758
2274
  }
1759
2275
  }
1760
- for (const node of doc.nodes) {
1761
- if (node.type === "heading") {
1762
- let headingLevel = HeadingLevel.HEADING_1;
1763
- if (node.level === 2) headingLevel = HeadingLevel.HEADING_2;
1764
- if (node.level === 3) headingLevel = HeadingLevel.HEADING_3;
1765
- if (node.level === 4) headingLevel = HeadingLevel.HEADING_4;
1766
- if (node.level === 5) headingLevel = HeadingLevel.HEADING_5;
1767
- if (node.level === 6) headingLevel = HeadingLevel.HEADING_6;
1768
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
1769
- docElements.push(
2276
+ if (resolved.toc) {
2277
+ const headingNodes = doc.nodes.filter(
2278
+ (n) => n.type === "heading" && typeof n.level === "number" && n.level >= 1 && n.level <= 3
2279
+ );
2280
+ if (headingNodes.length > 0) {
2281
+ const tocParagraphs = [
1770
2282
  new Paragraph({
1771
- heading: headingLevel,
1772
- children: runs,
1773
- spacing: { before: 240, after: 120 }
2283
+ children: [
2284
+ new TextRun({
2285
+ text: "TABLE OF CONTENTS",
2286
+ bold: true,
2287
+ size: 18,
2288
+ // 9pt
2289
+ color: textMutedHex,
2290
+ font: defaultFont
2291
+ })
2292
+ ],
2293
+ spacing: { after: 120 },
2294
+ border: {
2295
+ bottom: {
2296
+ color: borderHex,
2297
+ space: 6,
2298
+ style: BorderStyle.SINGLE,
2299
+ size: 4
2300
+ }
2301
+ }
1774
2302
  })
1775
- );
2303
+ ];
2304
+ for (const h of headingNodes) {
2305
+ const indentLeft = (h.level - 1) * 240;
2306
+ tocParagraphs.push(
2307
+ new Paragraph({
2308
+ indent: { left: indentLeft },
2309
+ children: [
2310
+ new TextRun({
2311
+ text: h.text,
2312
+ bold: h.level === 1,
2313
+ color: primaryDarkHex,
2314
+ size: h.level === 1 ? 21 : 20,
2315
+ font: defaultFont
2316
+ })
2317
+ ],
2318
+ spacing: { before: 20, after: 20 }
2319
+ })
2320
+ );
2321
+ }
2322
+ const tocCard = new Table({
2323
+ width: { size: 100, type: WidthType.PERCENTAGE },
2324
+ columnWidths: [9e3],
2325
+ rows: [
2326
+ new TableRow({
2327
+ children: [
2328
+ new TableCell({
2329
+ width: { size: 9e3, type: WidthType.DXA },
2330
+ shading: { fill: cardBgHex, type: ShadingType.CLEAR },
2331
+ margins: { top: 140, bottom: 140, left: 180, right: 180 },
2332
+ borders: {
2333
+ top: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
2334
+ bottom: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
2335
+ left: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
2336
+ right: { style: BorderStyle.SINGLE, size: 4, color: borderHex }
2337
+ },
2338
+ children: tocParagraphs
2339
+ })
2340
+ ]
2341
+ })
2342
+ ]
2343
+ });
2344
+ docElements.push(tocCard);
2345
+ docElements.push(new Paragraph({ spacing: { after: 200 } }));
2346
+ }
2347
+ }
2348
+ for (const node of doc.nodes) {
2349
+ if (node.type === "heading") {
2350
+ if (node.level === 1) {
2351
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2352
+ font: defaultFont,
2353
+ size: 34,
2354
+ // 17pt
2355
+ color: textHex,
2356
+ bold: true
2357
+ });
2358
+ docElements.push(
2359
+ new Paragraph({
2360
+ heading: HeadingLevel.HEADING_1,
2361
+ children: runs,
2362
+ spacing: { before: 360, after: 140 },
2363
+ border: {
2364
+ bottom: {
2365
+ color: primaryHex,
2366
+ space: 6,
2367
+ style: BorderStyle.SINGLE,
2368
+ size: 16
2369
+ }
2370
+ }
2371
+ })
2372
+ );
2373
+ } else if (node.level === 2) {
2374
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2375
+ font: defaultFont,
2376
+ size: 26,
2377
+ // 13pt
2378
+ color: primaryDarkHex,
2379
+ bold: true
2380
+ });
2381
+ docElements.push(
2382
+ new Paragraph({
2383
+ heading: HeadingLevel.HEADING_2,
2384
+ children: runs,
2385
+ spacing: { before: 280, after: 100 },
2386
+ border: {
2387
+ bottom: {
2388
+ color: borderHex,
2389
+ space: 4,
2390
+ style: BorderStyle.SINGLE,
2391
+ size: 4
2392
+ }
2393
+ }
2394
+ })
2395
+ );
2396
+ } else {
2397
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2398
+ font: defaultFont,
2399
+ size: 22,
2400
+ // 11pt
2401
+ color: textHex,
2402
+ bold: true
2403
+ });
2404
+ docElements.push(
2405
+ new Paragraph({
2406
+ heading: node.level === 3 ? HeadingLevel.HEADING_3 : HeadingLevel.HEADING_4,
2407
+ children: runs,
2408
+ spacing: { before: 200, after: 80 }
2409
+ })
2410
+ );
2411
+ }
1776
2412
  continue;
1777
2413
  }
1778
2414
  if (node.type === "paragraph") {
1779
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
2415
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2416
+ font: defaultFont,
2417
+ size: 22,
2418
+ // 11pt
2419
+ color: "334155"
2420
+ });
1780
2421
  docElements.push(
1781
2422
  new Paragraph({
1782
2423
  children: runs,
1783
- spacing: { before: 60, after: 140 }
2424
+ spacing: { before: 40, after: 140, line: 280 }
1784
2425
  })
1785
2426
  );
1786
2427
  continue;
@@ -1852,7 +2493,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1852
2493
  bgFill = "F5F3FF";
1853
2494
  title = "IMPORTANT";
1854
2495
  }
1855
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
2496
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2497
+ font: defaultFont,
2498
+ size: 21,
2499
+ color: "334155"
2500
+ });
1856
2501
  const calloutTable = new Table({
1857
2502
  width: { size: 100, type: WidthType.PERCENTAGE },
1858
2503
  columnWidths: [9e3],
@@ -1867,7 +2512,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1867
2512
  top: { style: BorderStyle.NONE },
1868
2513
  bottom: { style: BorderStyle.NONE },
1869
2514
  right: { style: BorderStyle.NONE },
1870
- left: { style: BorderStyle.SINGLE, size: 16, color: borderColor }
2515
+ left: { style: BorderStyle.SINGLE, size: 20, color: borderColor }
1871
2516
  },
1872
2517
  children: [
1873
2518
  new Paragraph({
@@ -1876,13 +2521,15 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1876
2521
  text: `[${title}]`,
1877
2522
  bold: true,
1878
2523
  color: borderColor,
2524
+ font: defaultFont,
1879
2525
  size: 20
1880
2526
  })
1881
2527
  ],
1882
- spacing: { after: 60 }
2528
+ spacing: { after: 40 }
1883
2529
  }),
1884
2530
  new Paragraph({
1885
- children: runs
2531
+ children: runs,
2532
+ spacing: { line: 270 }
1886
2533
  })
1887
2534
  ]
1888
2535
  })
@@ -1900,17 +2547,27 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1900
2547
  if (lines.length > 0) {
1901
2548
  for (const lineText of lines) {
1902
2549
  const spans = parseInlineSpans(lineText.replace(/^>\s?/, "").trim());
1903
- const lineRuns = await convertInlinesToTextRuns(spans, baseDir);
2550
+ const lineRuns = await convertInlinesToTextRuns(spans, baseDir, {
2551
+ font: defaultFont,
2552
+ size: 21,
2553
+ color: "475569",
2554
+ italics: true
2555
+ });
1904
2556
  quoteParagraphs.push(
1905
2557
  new Paragraph({
1906
2558
  children: lineRuns,
1907
- spacing: { before: 40, after: 40 }
2559
+ spacing: { before: 20, after: 20, line: 260 }
1908
2560
  })
1909
2561
  );
1910
2562
  }
1911
2563
  } else {
1912
- const runs = await convertInlinesToTextRuns(node.inlines, baseDir);
1913
- quoteParagraphs.push(new Paragraph({ children: runs }));
2564
+ const runs = await convertInlinesToTextRuns(node.inlines, baseDir, {
2565
+ font: defaultFont,
2566
+ size: 21,
2567
+ color: "475569",
2568
+ italics: true
2569
+ });
2570
+ quoteParagraphs.push(new Paragraph({ children: runs, spacing: { line: 260 } }));
1914
2571
  }
1915
2572
  const quoteTable = new Table({
1916
2573
  width: { size: 100, type: WidthType.PERCENTAGE },
@@ -1926,7 +2583,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1926
2583
  top: { style: BorderStyle.NONE },
1927
2584
  bottom: { style: BorderStyle.NONE },
1928
2585
  right: { style: BorderStyle.NONE },
1929
- left: { style: BorderStyle.SINGLE, size: 12, color: "33CDCF" }
2586
+ left: { style: BorderStyle.SINGLE, size: 16, color: primaryHex }
1930
2587
  },
1931
2588
  children: quoteParagraphs
1932
2589
  })
@@ -1961,7 +2618,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1961
2618
  docElements.push(
1962
2619
  new Paragraph({
1963
2620
  children: [
1964
- new TextRun({ text: "[Mermaid Diagram: " + (node.text || "").slice(0, 40) + "...]", bold: true, color: "33CDCF" })
2621
+ new TextRun({ text: "[Mermaid Diagram: " + (node.text || "").slice(0, 40) + "...]", bold: true, color: primaryHex })
1965
2622
  ],
1966
2623
  spacing: { before: 60, after: 60 }
1967
2624
  })
@@ -1973,8 +2630,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1973
2630
  const tableRows = [];
1974
2631
  const numCols = ((_b = (_a = node.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) || 1;
1975
2632
  const colWidth = Math.floor(9e3 / numCols);
1976
- for (const rowNode of node.children) {
2633
+ for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2634
+ const rowNode = node.children[rowIdx];
1977
2635
  const cells = [];
2636
+ const isHeader = rowNode.isHeader;
2637
+ const rowBg = isHeader ? "F1F5F9" : rowIdx % 2 === 0 ? "FFFFFF" : "F8FAFC";
1978
2638
  if (rowNode.children) {
1979
2639
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
1980
2640
  const cellNode = rowNode.children[colIdx];
@@ -1982,22 +2642,27 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
1982
2642
  let alignment = AlignmentType.LEFT;
1983
2643
  if (align === "center") alignment = AlignmentType.CENTER;
1984
2644
  if (align === "right") alignment = AlignmentType.RIGHT;
1985
- const runs = await convertInlinesToTextRuns(cellNode.inlines, baseDir);
2645
+ const runs = await convertInlinesToTextRuns(cellNode.inlines, baseDir, {
2646
+ font: defaultFont,
2647
+ size: 20,
2648
+ color: isHeader ? "0F172A" : "334155",
2649
+ bold: isHeader
2650
+ });
1986
2651
  cells.push(
1987
2652
  new TableCell({
1988
2653
  width: { size: colWidth, type: WidthType.DXA },
1989
- shading: rowNode.isHeader ? { fill: "F1F5F9", type: ShadingType.CLEAR } : void 0,
1990
- margins: { top: 100, bottom: 100, left: 120, right: 120 },
2654
+ shading: { fill: rowBg, type: ShadingType.CLEAR },
2655
+ margins: { top: 100, bottom: 100, left: 140, right: 140 },
1991
2656
  borders: {
1992
- top: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" },
1993
- bottom: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" },
1994
- left: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" },
1995
- right: { style: BorderStyle.SINGLE, size: 4, color: "CBD5E1" }
2657
+ top: { style: BorderStyle.SINGLE, size: 4, color: "E2E8F0" },
2658
+ bottom: { style: BorderStyle.SINGLE, size: isHeader ? 8 : 4, color: isHeader ? "CBD5E1" : "E2E8F0" },
2659
+ left: { style: BorderStyle.SINGLE, size: 4, color: "E2E8F0" },
2660
+ right: { style: BorderStyle.SINGLE, size: 4, color: "E2E8F0" }
1996
2661
  },
1997
2662
  children: [
1998
2663
  new Paragraph({
1999
2664
  alignment,
2000
- children: rowNode.isHeader ? runs.map((r) => r instanceof TextRun ? new TextRun({ ...r, bold: true, color: "0F172A" }) : r) : runs
2665
+ children: runs
2001
2666
  })
2002
2667
  ]
2003
2668
  })
@@ -2006,7 +2671,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2006
2671
  }
2007
2672
  tableRows.push(
2008
2673
  new TableRow({
2009
- tableHeader: rowNode.isHeader,
2674
+ tableHeader: isHeader,
2010
2675
  children: cells
2011
2676
  })
2012
2677
  );
@@ -2022,7 +2687,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2022
2687
  }
2023
2688
  if (node.type === "list" && node.children) {
2024
2689
  for (const item of node.children) {
2025
- const runs = await convertInlinesToTextRuns(item.inlines, baseDir);
2690
+ const runs = await convertInlinesToTextRuns(item.inlines, baseDir, {
2691
+ font: defaultFont,
2692
+ size: 21,
2693
+ color: "334155"
2694
+ });
2026
2695
  let prefix = "";
2027
2696
  if (item.checked !== void 0) {
2028
2697
  prefix = item.checked ? "[X] " : "[ ] ";
@@ -2034,7 +2703,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2034
2703
  ...prefix ? [new TextRun({ text: prefix, bold: true, font: "Consolas" })] : [],
2035
2704
  ...runs
2036
2705
  ],
2037
- spacing: { before: 40, after: 40 }
2706
+ spacing: { before: 20, after: 20, line: 260 }
2038
2707
  })
2039
2708
  );
2040
2709
  }
@@ -2109,88 +2778,205 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2109
2778
  continue;
2110
2779
  }
2111
2780
  }
2112
- const headerObj = metadata.header || config.header;
2113
- const footerObj = metadata.footer || config.footer;
2114
- const headerTabStops = [
2115
- { type: TabStopType.CENTER, position: 4513 },
2116
- { type: TabStopType.RIGHT, position: 9026 }
2117
- ];
2118
- const docHeader = headerObj ? new Header({
2781
+ const contentWidthTwip = Math.max(
2782
+ 1e3,
2783
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2784
+ );
2785
+ const centerPos = Math.round(contentWidthTwip / 2);
2786
+ const rightPos = contentWidthTwip;
2787
+ const headerRuns = [];
2788
+ if ((_d = resolved.header) == null ? void 0 : _d.left) {
2789
+ headerRuns.push(
2790
+ new TextRun({
2791
+ text: resolved.header.left.text,
2792
+ color: resolved.header.left.color.replace("#", ""),
2793
+ size: (resolved.header.left.fontSize || 9) * 2,
2794
+ font: resolved.header.left.fontFamily || defaultFont,
2795
+ bold: resolved.header.left.bold,
2796
+ italics: resolved.header.left.italic
2797
+ })
2798
+ );
2799
+ }
2800
+ headerRuns.push(new TextRun({ text: " " }));
2801
+ if ((_e = resolved.header) == null ? void 0 : _e.center) {
2802
+ headerRuns.push(
2803
+ new TextRun({
2804
+ text: resolved.header.center.text,
2805
+ color: resolved.header.center.color.replace("#", ""),
2806
+ size: (resolved.header.center.fontSize || 9) * 2,
2807
+ font: resolved.header.center.fontFamily || defaultFont,
2808
+ bold: resolved.header.center.bold,
2809
+ italics: resolved.header.center.italic
2810
+ })
2811
+ );
2812
+ }
2813
+ headerRuns.push(new TextRun({ text: " " }));
2814
+ if ((_f = resolved.header) == null ? void 0 : _f.right) {
2815
+ headerRuns.push(
2816
+ new TextRun({
2817
+ text: resolved.header.right.text,
2818
+ color: resolved.header.right.color.replace("#", ""),
2819
+ size: (resolved.header.right.fontSize || 9) * 2,
2820
+ font: resolved.header.right.fontFamily || defaultFont,
2821
+ bold: resolved.header.right.bold,
2822
+ italics: resolved.header.right.italic
2823
+ })
2824
+ );
2825
+ }
2826
+ const docHeader = resolved.header ? new Header({
2119
2827
  children: [
2120
2828
  new Paragraph({
2121
- tabStops: headerTabStops,
2122
- children: [
2123
- // Left zone
2124
- ...headerObj.left ? [
2829
+ tabStops: [
2830
+ {
2831
+ type: TabStopType.CENTER,
2832
+ position: centerPos
2833
+ },
2834
+ {
2835
+ type: TabStopType.RIGHT,
2836
+ position: rightPos
2837
+ }
2838
+ ],
2839
+ border: resolved.header.divider ? {
2840
+ bottom: {
2841
+ style: BorderStyle.SINGLE,
2842
+ size: 4,
2843
+ space: 6,
2844
+ color: (resolved.header.dividerColor || "#CBD5E1").replace("#", "")
2845
+ }
2846
+ } : void 0,
2847
+ children: headerRuns,
2848
+ spacing: { after: 120 }
2849
+ })
2850
+ ]
2851
+ }) : void 0;
2852
+ const footerRuns = [];
2853
+ if ((_g = resolved.footer) == null ? void 0 : _g.left) {
2854
+ footerRuns.push(
2855
+ new TextRun({
2856
+ text: resolved.footer.left.text,
2857
+ color: resolved.footer.left.color.replace("#", ""),
2858
+ size: (resolved.footer.left.fontSize || 9) * 2,
2859
+ font: resolved.footer.left.fontFamily || defaultFont,
2860
+ bold: resolved.footer.left.bold,
2861
+ italics: resolved.footer.left.italic
2862
+ })
2863
+ );
2864
+ }
2865
+ footerRuns.push(new TextRun({ text: " " }));
2866
+ if ((_h = resolved.footer) == null ? void 0 : _h.center) {
2867
+ footerRuns.push(
2868
+ new TextRun({
2869
+ text: resolved.footer.center.text,
2870
+ color: resolved.footer.center.color.replace("#", ""),
2871
+ size: (resolved.footer.center.fontSize || 9) * 2,
2872
+ font: resolved.footer.center.fontFamily || defaultFont,
2873
+ bold: resolved.footer.center.bold,
2874
+ italics: resolved.footer.center.italic
2875
+ })
2876
+ );
2877
+ }
2878
+ footerRuns.push(new TextRun({ text: " " }));
2879
+ if ((_i = resolved.footer) == null ? void 0 : _i.right) {
2880
+ const rZone = resolved.footer.right;
2881
+ const rColor = rZone.color.replace("#", "");
2882
+ const rSize = (rZone.fontSize || 9) * 2;
2883
+ const rFont = rZone.fontFamily || defaultFont;
2884
+ const rBold = rZone.bold;
2885
+ const rItalics = rZone.italic;
2886
+ if (rZone.text.includes("{page}") || rZone.text.includes("{pages}")) {
2887
+ const parts = rZone.text.split(/(\{page\}|\{pages\})/gi);
2888
+ for (const part of parts) {
2889
+ if (part.toLowerCase() === "{page}") {
2890
+ footerRuns.push(
2125
2891
  new TextRun({
2126
- text: headerObj.left.replace("{title}", metadata.title || ""),
2127
- color: "94A3B8",
2128
- size: 18
2892
+ children: [PageNumber.CURRENT],
2893
+ color: rColor,
2894
+ size: rSize,
2895
+ font: rFont,
2896
+ bold: rBold,
2897
+ italics: rItalics
2129
2898
  })
2130
- ] : [],
2131
- // Center zone (tab + text)
2132
- ...headerObj.center ? [
2133
- new TextRun({ text: " ", color: "94A3B8", size: 18 }),
2899
+ );
2900
+ } else if (part.toLowerCase() === "{pages}") {
2901
+ footerRuns.push(
2134
2902
  new TextRun({
2135
- text: headerObj.center.replace("{title}", metadata.title || ""),
2136
- color: "94A3B8",
2137
- size: 18
2903
+ children: [PageNumber.TOTAL_PAGES],
2904
+ color: rColor,
2905
+ size: rSize,
2906
+ font: rFont,
2907
+ bold: rBold,
2908
+ italics: rItalics
2138
2909
  })
2139
- ] : [],
2140
- // Right zone (tab + text) — skip extra tab if center already used one
2141
- ...headerObj.right ? [
2142
- new TextRun({
2143
- text: headerObj.left || headerObj.center ? " " : "",
2144
- color: "94A3B8",
2145
- size: 18
2146
- }),
2910
+ );
2911
+ } else if (part) {
2912
+ footerRuns.push(
2147
2913
  new TextRun({
2148
- text: headerObj.right.replace("{title}", metadata.title || ""),
2149
- color: "94A3B8",
2150
- size: 18
2914
+ text: part,
2915
+ color: rColor,
2916
+ size: rSize,
2917
+ font: rFont,
2918
+ bold: rBold,
2919
+ italics: rItalics
2151
2920
  })
2152
- ] : []
2153
- ]
2154
- })
2155
- ]
2156
- }) : void 0;
2157
- const docFooter = footerObj ? new Footer({
2921
+ );
2922
+ }
2923
+ }
2924
+ } else {
2925
+ footerRuns.push(
2926
+ new TextRun({
2927
+ text: rZone.text,
2928
+ color: rColor,
2929
+ size: rSize,
2930
+ font: rFont,
2931
+ bold: rBold,
2932
+ italics: rItalics
2933
+ })
2934
+ );
2935
+ }
2936
+ }
2937
+ const docFooter = resolved.footer ? new Footer({
2158
2938
  children: [
2159
2939
  new Paragraph({
2160
- tabStops: headerTabStops,
2161
- children: [
2162
- // Left zone
2163
- ...footerObj.left ? [
2164
- new TextRun({
2165
- text: footerObj.left.replace("{page}", "").replace("{pages}", "").trim(),
2166
- color: "94A3B8",
2167
- size: 18
2168
- })
2169
- ] : [],
2170
- // Right zone: always includes page number if right is configured or footer exists
2171
- new TextRun({ text: " ", color: "94A3B8", size: 18 }),
2172
- new TextRun({ text: "Page ", color: "94A3B8", size: 18 }),
2173
- new TextRun({ children: [PageNumber.CURRENT], color: "94A3B8", size: 18 }),
2174
- new TextRun({ text: " of ", color: "94A3B8", size: 18 }),
2175
- new TextRun({ children: [PageNumber.TOTAL_PAGES], color: "94A3B8", size: 18 })
2176
- ]
2940
+ tabStops: [
2941
+ {
2942
+ type: TabStopType.CENTER,
2943
+ position: centerPos
2944
+ },
2945
+ {
2946
+ type: TabStopType.RIGHT,
2947
+ position: rightPos
2948
+ }
2949
+ ],
2950
+ border: resolved.footer.divider ? {
2951
+ top: {
2952
+ style: BorderStyle.SINGLE,
2953
+ size: 4,
2954
+ space: 6,
2955
+ color: (resolved.footer.dividerColor || "#CBD5E1").replace("#", "")
2956
+ }
2957
+ } : void 0,
2958
+ children: footerRuns,
2959
+ spacing: { before: 120 }
2177
2960
  })
2178
2961
  ]
2179
2962
  }) : void 0;
2180
- const topMargin = parseMarginToTwip(((_d = metadata.margins) == null ? void 0 : _d.top) || ((_e = config.margins) == null ? void 0 : _e.top), 1440);
2181
- const bottomMargin = parseMarginToTwip(((_f = metadata.margins) == null ? void 0 : _f.bottom) || ((_g = config.margins) == null ? void 0 : _g.bottom), 1440);
2182
- const leftMargin = parseMarginToTwip(((_h = metadata.margins) == null ? void 0 : _h.left) || ((_i = config.margins) == null ? void 0 : _i.left), 1440);
2183
- const rightMargin = parseMarginToTwip(((_j = metadata.margins) == null ? void 0 : _j.right) || ((_k = config.margins) == null ? void 0 : _k.right), 1440);
2184
- const isLandscape = (metadata.orientation || config.orientation) === "landscape";
2963
+ const isLandscape = resolved.orientation === "landscape";
2185
2964
  const document = new Document({
2186
2965
  styles: {
2187
2966
  default: {
2188
2967
  document: {
2189
2968
  run: {
2190
- font: "Segoe UI",
2191
- size: 22,
2192
- // 11pt
2193
- color: "0F172A"
2969
+ font: defaultFont,
2970
+ size: 21,
2971
+ // 10.5pt
2972
+ color: textHex
2973
+ },
2974
+ paragraph: {
2975
+ spacing: {
2976
+ line: 276,
2977
+ // 1.15 line spacing
2978
+ after: 140
2979
+ }
2194
2980
  }
2195
2981
  }
2196
2982
  }
@@ -2200,13 +2986,15 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2200
2986
  properties: {
2201
2987
  page: {
2202
2988
  size: {
2989
+ width: resolved.paperDimensions.widthTwip,
2990
+ height: resolved.paperDimensions.heightTwip,
2203
2991
  orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
2204
2992
  },
2205
2993
  margin: {
2206
- top: topMargin,
2207
- bottom: bottomMargin,
2208
- left: leftMargin,
2209
- right: rightMargin,
2994
+ top: resolved.margins.topTwip,
2995
+ bottom: resolved.margins.bottomTwip,
2996
+ left: resolved.margins.leftTwip,
2997
+ right: resolved.margins.rightTwip,
2210
2998
  header: 720,
2211
2999
  footer: 720
2212
3000
  }
@@ -2221,154 +3009,6 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2221
3009
  return await Packer.toBuffer(document);
2222
3010
  }
2223
3011
 
2224
- // src/config/loadConfig.ts
2225
- import * as fs5 from "fs";
2226
- import * as path5 from "path";
2227
- import { pathToFileURL as pathToFileURL3 } from "url";
2228
- import * as YAML from "yaml";
2229
- var DEFAULT_CONFIG_FILENAMES = [
2230
- "markforge.config.json",
2231
- ".markforgerc.json",
2232
- "markforge.config.yaml",
2233
- "markforge.config.yml",
2234
- ".markforgerc.yaml",
2235
- ".markforgerc.yml",
2236
- ".markforgerc",
2237
- "markforge.config.ts",
2238
- "markforge.config.js",
2239
- "markforge.config.mjs",
2240
- "markforge.config.cjs"
2241
- ];
2242
- var DEFAULT_CONFIG = {
2243
- to: ["docx", "pdf"],
2244
- outputDir: void 0,
2245
- theme: "default",
2246
- css: void 0,
2247
- orientation: "portrait",
2248
- paperSize: "A4",
2249
- margins: {
2250
- top: "2.5cm",
2251
- bottom: "2.5cm",
2252
- left: "2.5cm",
2253
- right: "2.5cm"
2254
- },
2255
- header: void 0,
2256
- footer: {
2257
- right: "Page {page} of {pages}"
2258
- },
2259
- toc: false,
2260
- watermark: void 0,
2261
- embedImages: true,
2262
- metadata: void 0,
2263
- watch: false,
2264
- serve: false,
2265
- port: 4e3,
2266
- open: false,
2267
- bundleHtml: true,
2268
- syntaxTheme: "github-dark"
2269
- };
2270
- function discoverConfigFile(startDir) {
2271
- const dirsToCheck = [];
2272
- let curr = path5.resolve(startDir);
2273
- while (curr) {
2274
- dirsToCheck.push(curr);
2275
- const parent = path5.dirname(curr);
2276
- if (parent === curr) break;
2277
- curr = parent;
2278
- }
2279
- const cwd = path5.resolve(process.cwd());
2280
- if (!dirsToCheck.includes(cwd)) {
2281
- dirsToCheck.push(cwd);
2282
- }
2283
- for (const dir of dirsToCheck) {
2284
- for (const filename of DEFAULT_CONFIG_FILENAMES) {
2285
- const candidate = path5.join(dir, filename);
2286
- if (fs5.existsSync(candidate) && fs5.statSync(candidate).isFile()) {
2287
- return candidate;
2288
- }
2289
- }
2290
- }
2291
- return null;
2292
- }
2293
- async function loadConfig(customPath, startDir = process.cwd()) {
2294
- let resolvedPath = null;
2295
- if (customPath) {
2296
- resolvedPath = path5.isAbsolute(customPath) ? customPath : path5.resolve(process.cwd(), customPath);
2297
- if (!fs5.existsSync(resolvedPath)) {
2298
- const altPath = path5.resolve(startDir, customPath);
2299
- if (fs5.existsSync(altPath)) {
2300
- resolvedPath = altPath;
2301
- } else {
2302
- throw new Error(`Configuration file not found: ${resolvedPath}`);
2303
- }
2304
- }
2305
- } else {
2306
- resolvedPath = discoverConfigFile(startDir);
2307
- }
2308
- if (!resolvedPath) {
2309
- return {
2310
- config: { ...DEFAULT_CONFIG },
2311
- configPath: null
2312
- };
2313
- }
2314
- const ext = path5.extname(resolvedPath).toLowerCase();
2315
- let userConfig = {};
2316
- try {
2317
- if (ext === ".json" || ext === "" || resolvedPath.endsWith(".markforgerc")) {
2318
- const raw = fs5.readFileSync(resolvedPath, "utf-8").trim();
2319
- try {
2320
- userConfig = JSON.parse(raw);
2321
- } catch {
2322
- userConfig = YAML.parse(raw) || {};
2323
- }
2324
- } else if (ext === ".yaml" || ext === ".yml") {
2325
- const raw = fs5.readFileSync(resolvedPath, "utf-8");
2326
- userConfig = YAML.parse(raw) || {};
2327
- } else if (ext === ".ts" || ext === ".js" || ext === ".mjs" || ext === ".cjs") {
2328
- try {
2329
- const fileUrl = `${pathToFileURL3(resolvedPath).href}?t=${Date.now()}`;
2330
- const mod = await import(fileUrl);
2331
- const rawExport = mod.default ?? mod.config ?? mod;
2332
- userConfig = (typeof rawExport === "function" ? await rawExport() : rawExport) || {};
2333
- } catch (importErr) {
2334
- try {
2335
- const mod = __require(resolvedPath);
2336
- const rawExport = mod.default ?? mod.config ?? mod;
2337
- userConfig = (typeof rawExport === "function" ? await rawExport() : rawExport) || {};
2338
- } catch {
2339
- throw importErr;
2340
- }
2341
- }
2342
- }
2343
- } catch (err) {
2344
- throw new Error(
2345
- `Failed to parse configuration file at ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`
2346
- );
2347
- }
2348
- if (userConfig && typeof userConfig === "object" && "$schema" in userConfig) {
2349
- delete userConfig.$schema;
2350
- }
2351
- const parsedConfig = userConfig;
2352
- const mergedConfig = {
2353
- ...DEFAULT_CONFIG,
2354
- ...parsedConfig,
2355
- margins: {
2356
- ...DEFAULT_CONFIG.margins,
2357
- ...parsedConfig.margins || {}
2358
- },
2359
- header: parsedConfig.header !== void 0 ? parsedConfig.header : DEFAULT_CONFIG.header,
2360
- footer: parsedConfig.footer !== void 0 ? parsedConfig.footer : DEFAULT_CONFIG.footer,
2361
- metadata: {
2362
- ...DEFAULT_CONFIG.metadata || {},
2363
- ...parsedConfig.metadata || {}
2364
- }
2365
- };
2366
- return {
2367
- config: mergedConfig,
2368
- configPath: resolvedPath
2369
- };
2370
- }
2371
-
2372
3012
  // src/core/engine.ts
2373
3013
  function formatServerTimestamp(date = /* @__PURE__ */ new Date()) {
2374
3014
  const pad = (n) => String(n).padStart(2, "0");
@@ -2469,7 +3109,7 @@ function defineConfig(config) {
2469
3109
  // src/version.ts
2470
3110
  import * as fs7 from "fs";
2471
3111
  import * as path7 from "path";
2472
- import { fileURLToPath as fileURLToPath2 } from "url";
3112
+ import { fileURLToPath } from "url";
2473
3113
  try {
2474
3114
  if (typeof globalThis !== "undefined" && (!globalThis.localStorage || typeof globalThis.localStorage.getItem !== "function")) {
2475
3115
  Object.defineProperty(globalThis, "localStorage", {
@@ -2515,7 +3155,7 @@ function getPackageDir() {
2515
3155
  return __dirname;
2516
3156
  }
2517
3157
  try {
2518
- return path7.dirname(fileURLToPath2(import.meta.url));
3158
+ return path7.dirname(fileURLToPath(import.meta.url));
2519
3159
  } catch {
2520
3160
  return process.cwd();
2521
3161
  }
@@ -2527,10 +3167,17 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
2527
3167
  export {
2528
3168
  DEFAULT_CONFIG,
2529
3169
  MARKFORGE_VERSION,
3170
+ Orientation,
3171
+ OutputFormat,
3172
+ PAPER_DIMENSIONS_TWIP,
3173
+ PaperSizeEnum,
2530
3174
  SYNTAX_COLORS,
3175
+ SyntaxTheme,
2531
3176
  THEMES,
2532
- THEME_ACADEMIC,
3177
+ THEME_CORPORATE,
2533
3178
  THEME_DEFAULT,
3179
+ Theme,
3180
+ WatermarkPosition,
2534
3181
  buildDocxDocument,
2535
3182
  buildHtmlDocument,
2536
3183
  buildPdfDocument,
@@ -2539,6 +3186,7 @@ export {
2539
3186
  escapeHtml,
2540
3187
  findChromeExecutable,
2541
3188
  formatServerTimestamp,
3189
+ generateThemeCss,
2542
3190
  getMarkforgeVersion,
2543
3191
  getMimeType,
2544
3192
  highlightCodeToHtml,
@@ -2546,11 +3194,16 @@ export {
2546
3194
  inlineHtmlImages,
2547
3195
  loadConfig,
2548
3196
  compileMarkdown as markforge,
3197
+ normalizeHeaderFooter,
3198
+ normalizeHeaderFooterSlot,
3199
+ normalizeWatermark,
2549
3200
  parseInlineSpans,
2550
- parseMarginToTwip,
3201
+ parseMarginToTwip2 as parseMarginToTwip,
2551
3202
  parseMarkdownDocument,
2552
3203
  renderInlinesToHtml,
2553
3204
  renderMermaidToPng,
3205
+ replaceDocumentTokens,
3206
+ resolveDocumentConfig,
2554
3207
  resolveImage,
2555
3208
  slugify,
2556
3209
  tokenizeCodeLine