@masumdev/markforge 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -14,8 +14,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
14
14
  });
15
15
 
16
16
  // src/core/engine.ts
17
- import * as fs6 from "fs";
18
- import * as path6 from "path";
17
+ import * as fs7 from "fs";
18
+ import * as path7 from "path";
19
19
 
20
20
  // src/core/parser.ts
21
21
  import matter from "gray-matter";
@@ -189,7 +189,16 @@ function parseInlineSpans(text) {
189
189
  remaining = remaining.slice(nextSpecial);
190
190
  }
191
191
  }
192
- return spans;
192
+ const merged = [];
193
+ for (const s of spans) {
194
+ const prev = merged[merged.length - 1];
195
+ if (prev && prev.type === "text" && s.type === "text") {
196
+ prev.content += s.content;
197
+ } else {
198
+ merged.push(s);
199
+ }
200
+ }
201
+ return merged;
193
202
  }
194
203
  function slugify(text) {
195
204
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -201,6 +210,7 @@ function applyHeadingNumbering(nodes, tocEntries, options) {
201
210
  const counters = [0, 0, 0, 0, 0, 0];
202
211
  for (const node of nodes) {
203
212
  if (node.type === "heading" && node.level) {
213
+ if (node._numbered) continue;
204
214
  const lvl = node.level;
205
215
  if (lvl > depth) continue;
206
216
  if (lvl === 1 && skipH1) continue;
@@ -216,6 +226,7 @@ function applyHeadingNumbering(nodes, tocEntries, options) {
216
226
  const originalText = node.text || "";
217
227
  node.text = fullPrefix + originalText;
218
228
  node.inlines = parseInlineSpans(node.text);
229
+ node._numbered = true;
219
230
  const toc = tocEntries.find((t) => t.id === node.id);
220
231
  if (toc) {
221
232
  toc.text = node.text;
@@ -578,12 +589,16 @@ import {
578
589
  convertMillimetersToTwip,
579
590
  ShadingType,
580
591
  ExternalHyperlink,
581
- TabStopType
592
+ TabStopType,
593
+ PageBreak,
594
+ NumberFormat,
595
+ SectionType
582
596
  } from "docx";
583
597
 
584
598
  // src/core/imageResolver.ts
585
599
  import * as fs from "fs";
586
600
  import * as path from "path";
601
+ import { fileURLToPath } from "url";
587
602
  var memoryImageCache = /* @__PURE__ */ new Map();
588
603
  function getMimeType(filePathOrUrl) {
589
604
  const clean = filePathOrUrl.split("?")[0].toLowerCase();
@@ -640,9 +655,19 @@ async function resolveImage(src, baseDir = process.cwd()) {
640
655
  memoryImageCache.set(cacheKey, resolved2);
641
656
  return resolved2;
642
657
  }
643
- let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
658
+ let localPath = src;
659
+ if (src.startsWith("file://")) {
660
+ try {
661
+ localPath = fileURLToPath(src);
662
+ } catch {
663
+ localPath = src;
664
+ }
665
+ }
666
+ if (!path.isAbsolute(localPath)) {
667
+ localPath = path.resolve(baseDir, localPath);
668
+ }
644
669
  if (!fs.existsSync(localPath)) {
645
- const cwdPath = path.resolve(process.cwd(), src);
670
+ const cwdPath = path.resolve(process.cwd(), src.startsWith("file://") ? localPath : src);
646
671
  if (fs.existsSync(cwdPath)) {
647
672
  localPath = cwdPath;
648
673
  } else {
@@ -1065,6 +1090,7 @@ var OutputFormat = /* @__PURE__ */ ((OutputFormat2) => {
1065
1090
  OutputFormat2["PDF"] = "pdf";
1066
1091
  OutputFormat2["HTML"] = "html";
1067
1092
  OutputFormat2["PNG"] = "png";
1093
+ OutputFormat2["TXT"] = "txt";
1068
1094
  return OutputFormat2;
1069
1095
  })(OutputFormat || {});
1070
1096
  var Theme = /* @__PURE__ */ ((Theme2) => {
@@ -1099,6 +1125,33 @@ var WatermarkPosition = /* @__PURE__ */ ((WatermarkPosition2) => {
1099
1125
  WatermarkPosition2["BOTTOM_RIGHT"] = "bottom-right";
1100
1126
  return WatermarkPosition2;
1101
1127
  })(WatermarkPosition || {});
1128
+ var CoverPagePreset = /* @__PURE__ */ ((CoverPagePreset2) => {
1129
+ CoverPagePreset2["MODERN"] = "modern";
1130
+ CoverPagePreset2["CORPORATE_SPLIT"] = "corporate-split";
1131
+ CoverPagePreset2["MINIMAL"] = "minimal";
1132
+ CoverPagePreset2["CARD"] = "card";
1133
+ return CoverPagePreset2;
1134
+ })(CoverPagePreset || {});
1135
+ var BackCoverPreset = /* @__PURE__ */ ((BackCoverPreset2) => {
1136
+ BackCoverPreset2["MODERN"] = "modern";
1137
+ BackCoverPreset2["CORPORATE"] = "corporate";
1138
+ BackCoverPreset2["MINIMAL"] = "minimal";
1139
+ BackCoverPreset2["CONTACT_CARD"] = "contact-card";
1140
+ return BackCoverPreset2;
1141
+ })(BackCoverPreset || {});
1142
+ var SignatureAlign = /* @__PURE__ */ ((SignatureAlign2) => {
1143
+ SignatureAlign2["LEFT"] = "left";
1144
+ SignatureAlign2["CENTER"] = "center";
1145
+ SignatureAlign2["RIGHT"] = "right";
1146
+ SignatureAlign2["SPACE_BETWEEN"] = "space-between";
1147
+ return SignatureAlign2;
1148
+ })(SignatureAlign || {});
1149
+ var SignatureStyle = /* @__PURE__ */ ((SignatureStyle2) => {
1150
+ SignatureStyle2["LINE"] = "line";
1151
+ SignatureStyle2["BOX"] = "box";
1152
+ SignatureStyle2["CLEAN"] = "clean";
1153
+ return SignatureStyle2;
1154
+ })(SignatureStyle || {});
1102
1155
 
1103
1156
  // src/core/html/htmlThemes.ts
1104
1157
  var THEME_COMPONENTS = `
@@ -1125,7 +1178,7 @@ var THEME_COMPONENTS = `
1125
1178
  .document-meta { font-size: 0.9rem; color: var(--mf-text-muted); display: flex; gap: 1.5rem; flex-wrap: wrap; }
1126
1179
 
1127
1180
  /* Table of Contents */
1128
- .table-of-contents { background: var(--mf-card-bg); border: 1px solid var(--mf-border); border-radius: 8px; padding: 1.5rem 2rem; margin: 2rem 0; }
1181
+ .table-of-contents { background: var(--mf-card-bg); border: 1px solid var(--mf-border); border-radius: 8px; padding: 1.5rem 2rem; margin: 2rem 0; page-break-after: always; break-after: page; }
1129
1182
  .table-of-contents h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--mf-text-muted); margin: 0 0 1rem 0; }
1130
1183
  .table-of-contents ul { list-style: none; padding: 0; margin: 0; }
1131
1184
  .table-of-contents li { padding: 0.25rem 0; }
@@ -1198,11 +1251,13 @@ var THEME_CORPORATE = `
1198
1251
  }
1199
1252
  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; }
1200
1253
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1201
- 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; }
1202
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1254
+ h1, h2, h3, h4, h5, h6 { color: var(--mf-primary-dark); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1255
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
1203
1256
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
1204
- h3 { font-size: 1.3rem; }
1205
- h4 { font-size: 1.1rem; }
1257
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
1258
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
1259
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
1260
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
1206
1261
  p { margin: 0.8rem 0; }
1207
1262
  `;
1208
1263
  var THEME_DEFAULT = THEME_CORPORATE;
@@ -1244,11 +1299,13 @@ function generateThemeCss(theme) {
1244
1299
  }
1245
1300
  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; }
1246
1301
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1247
- 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; }
1248
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1302
+ h1, h2, h3, h4, h5, h6 { color: var(--mf-primary-dark); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1303
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
1249
1304
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid var(--mf-border); padding-bottom: 0.4rem; }
1250
- h3 { font-size: 1.3rem; }
1251
- h4 { font-size: 1.1rem; }
1305
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
1306
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
1307
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
1308
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
1252
1309
  p { margin: 0.8rem 0; }
1253
1310
  ${theme.customCss || ""}
1254
1311
  `;
@@ -1457,16 +1514,45 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1457
1514
  if (/^[0-9.]+$/.test(str)) return `${str}pt`;
1458
1515
  return str;
1459
1516
  }
1460
- function replaceDocumentTokens(template = "", meta) {
1461
- const currentYear = meta.year || (/* @__PURE__ */ new Date()).getFullYear().toString();
1462
- return template.replace(/\{title\}/gi, meta.title || "").replace(/\{subtitle\}/gi, meta.subtitle || "").replace(/\{author\}/gi, meta.author || "").replace(/\{version\}/gi, meta.version || "").replace(/\{date\}/gi, meta.date || "").replace(/\{year\}/gi, currentYear).replace(/\{company\}/gi, meta.company || "");
1517
+ function replaceDocumentTokens(template = "", meta = {}) {
1518
+ if (!template) return "";
1519
+ const currentYear = meta.year ? String(meta.year) : (/* @__PURE__ */ new Date()).getFullYear().toString();
1520
+ const tokenMap = {
1521
+ title: meta.title ? String(meta.title) : "",
1522
+ subtitle: meta.subtitle ? String(meta.subtitle) : "",
1523
+ author: meta.author ? Array.isArray(meta.author) ? meta.author.join(", ") : String(meta.author) : "",
1524
+ version: meta.version ? String(meta.version) : "",
1525
+ date: meta.date ? String(meta.date) : "",
1526
+ company: meta.company ? String(meta.company) : "",
1527
+ year: currentYear
1528
+ };
1529
+ if (meta.metadata && typeof meta.metadata === "object") {
1530
+ for (const [key, val] of Object.entries(meta.metadata)) {
1531
+ if (val !== void 0 && val !== null) {
1532
+ tokenMap[key.toLowerCase()] = String(val);
1533
+ }
1534
+ }
1535
+ }
1536
+ for (const [key, val] of Object.entries(meta)) {
1537
+ if (val !== void 0 && val !== null && typeof val !== "object") {
1538
+ tokenMap[key.toLowerCase()] = String(val);
1539
+ }
1540
+ }
1541
+ return template.replace(/\{([a-zA-Z0-9_\-]+)\}/gi, (match, tokenKey) => {
1542
+ const lowerKey = tokenKey.toLowerCase();
1543
+ if (lowerKey in tokenMap) {
1544
+ return tokenMap[lowerKey];
1545
+ }
1546
+ return match;
1547
+ });
1463
1548
  }
1464
- function normalizeWatermark(rawWatermark) {
1549
+ function normalizeWatermark(rawWatermark, tokens) {
1465
1550
  if (!rawWatermark) {
1466
1551
  return void 0;
1467
1552
  }
1468
1553
  if (typeof rawWatermark === "string") {
1469
- const text = rawWatermark.trim();
1554
+ let text = rawWatermark.trim();
1555
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1470
1556
  if (!text) return void 0;
1471
1557
  return {
1472
1558
  text,
@@ -1479,8 +1565,10 @@ function normalizeWatermark(rawWatermark) {
1479
1565
  }
1480
1566
  if (typeof rawWatermark === "object") {
1481
1567
  if (!rawWatermark.text || !rawWatermark.text.trim()) return void 0;
1568
+ let text = rawWatermark.text.trim();
1569
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1482
1570
  return {
1483
- text: rawWatermark.text.trim(),
1571
+ text,
1484
1572
  color: rawWatermark.color || "#94a3b8",
1485
1573
  opacity: typeof rawWatermark.opacity === "number" ? rawWatermark.opacity : 0.08,
1486
1574
  fontSize: rawWatermark.fontSize || 54,
@@ -1601,27 +1689,46 @@ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1601
1689
  const cfg = typeof rawCover === "object" ? rawCover : {};
1602
1690
  if (cfg.enabled === false) return void 0;
1603
1691
  const preset = cfg.preset || "modern";
1604
- const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title || "Document Title";
1605
- const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle;
1606
- const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author;
1607
- const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1608
- const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version;
1692
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title ? String(tokenCtx.title) : "Document Title";
1693
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle ? String(tokenCtx.subtitle) : void 0;
1694
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1695
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1696
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1609
1697
  let dateStr;
1610
1698
  if (typeof cfg.date === "string") {
1611
1699
  dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
1612
1700
  } else if (cfg.date === true) {
1613
- dateStr = tokenCtx.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1701
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1614
1702
  } else {
1615
- dateStr = tokenCtx.date;
1703
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : void 0;
1616
1704
  }
1617
1705
  const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1618
1706
  const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1619
1707
  const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1620
1708
  const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1621
1709
  const logoWidth = cfg.logoWidth;
1622
- const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1710
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1711
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1623
1712
  const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1713
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1714
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1715
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1624
1716
  const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1717
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1718
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1719
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1720
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1721
+ let socialMap;
1722
+ if (cfg.social && typeof cfg.social === "object") {
1723
+ socialMap = {};
1724
+ for (const [k, v] of Object.entries(cfg.social)) {
1725
+ if (typeof v === "string") {
1726
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1727
+ }
1728
+ }
1729
+ }
1730
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1731
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : void 0;
1625
1732
  return {
1626
1733
  enabled: true,
1627
1734
  preset,
@@ -1637,8 +1744,18 @@ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1637
1744
  logo,
1638
1745
  logoWidth,
1639
1746
  bgGradient,
1747
+ backgroundColor,
1640
1748
  textColor,
1641
- footerText
1749
+ titleColor,
1750
+ subtitleColor,
1751
+ accentColor,
1752
+ footerText,
1753
+ address,
1754
+ email,
1755
+ phone,
1756
+ website,
1757
+ social: socialMap,
1758
+ copyright
1642
1759
  };
1643
1760
  }
1644
1761
  function normalizeBackCover(rawBack, tokenCtx = {}) {
@@ -1648,7 +1765,10 @@ function normalizeBackCover(rawBack, tokenCtx = {}) {
1648
1765
  const preset = cfg.preset || "modern";
1649
1766
  const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
1650
1767
  const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
1651
- const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1768
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1769
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1770
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1771
+ const date = typeof cfg.date === "string" ? replaceDocumentTokens(cfg.date, tokenCtx) : tokenCtx.date ? String(tokenCtx.date) : void 0;
1652
1772
  const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1653
1773
  const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1654
1774
  const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
@@ -1664,32 +1784,45 @@ function normalizeBackCover(rawBack, tokenCtx = {}) {
1664
1784
  }
1665
1785
  const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1666
1786
  const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
1787
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1667
1788
  const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1668
1789
  const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1669
1790
  const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1670
1791
  const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1671
1792
  const logoWidth = cfg.logoWidth;
1672
- const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1793
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1794
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1673
1795
  const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1796
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1797
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1798
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1674
1799
  return {
1675
1800
  enabled: true,
1676
1801
  preset,
1677
1802
  title,
1678
1803
  subtitle,
1804
+ author,
1679
1805
  company,
1806
+ version,
1807
+ date,
1680
1808
  address,
1681
1809
  email,
1682
1810
  phone,
1683
1811
  website,
1684
1812
  social: socialMap,
1685
1813
  copyright,
1814
+ footerText,
1686
1815
  badge,
1687
1816
  badgeColor,
1688
1817
  badgeTextColor,
1689
1818
  logo,
1690
1819
  logoWidth,
1691
1820
  bgGradient,
1692
- textColor
1821
+ backgroundColor,
1822
+ textColor,
1823
+ titleColor,
1824
+ subtitleColor,
1825
+ accentColor
1693
1826
  };
1694
1827
  }
1695
1828
  function normalizeNumberHeadings(raw) {
@@ -1729,7 +1862,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1729
1862
  const version = mergedMeta.version || void 0;
1730
1863
  const company = mergedMeta.company || void 0;
1731
1864
  const lang = mergedMeta.lang || "en";
1732
- const tokenContext = { title, subtitle, author, version, date, company };
1865
+ const tokenContext = {
1866
+ ...mergedMeta,
1867
+ title,
1868
+ subtitle,
1869
+ author,
1870
+ version,
1871
+ date,
1872
+ company
1873
+ };
1733
1874
  const theme = mergedMeta.theme || userConfig.theme || DEFAULT_CONFIG.theme;
1734
1875
  const orientation = mergedMeta.orientation || userConfig.orientation || DEFAULT_CONFIG.orientation;
1735
1876
  const paperSize = mergedMeta.paperSize || userConfig.paperSize || DEFAULT_CONFIG.paperSize;
@@ -1758,7 +1899,7 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1758
1899
  const footer = normalizeHeaderFooter(rawFooter, tokenContext);
1759
1900
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1760
1901
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1761
- const watermark = normalizeWatermark(rawWatermark);
1902
+ const watermark = normalizeWatermark(rawWatermark, tokenContext);
1762
1903
  const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1763
1904
  const signatures = normalizeSignatures(rawSignatures, tokenContext);
1764
1905
  const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
@@ -1854,7 +1995,7 @@ var KATEX_INLINE_CSS = `
1854
1995
  function escapeHtml(str) {
1855
1996
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1856
1997
  }
1857
- async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1998
+ async function renderInlinesToHtml(spans = [], baseDir = process.cwd(), tokens) {
1858
1999
  let result = "";
1859
2000
  for (const span of spans) {
1860
2001
  if (span.type === "image" && span.url) {
@@ -1868,23 +2009,23 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1868
2009
  continue;
1869
2010
  }
1870
2011
  if (span.type === "link" && span.url) {
1871
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2012
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1872
2013
  const title = span.title ? ` title="${escapeHtml(span.title)}"` : "";
1873
2014
  result += `<a href="${escapeHtml(span.url)}"${title}>${inner}</a>`;
1874
2015
  continue;
1875
2016
  }
1876
2017
  if (span.type === "bold") {
1877
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2018
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1878
2019
  result += `<strong>${inner}</strong>`;
1879
2020
  continue;
1880
2021
  }
1881
2022
  if (span.type === "italic") {
1882
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2023
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1883
2024
  result += `<em>${inner}</em>`;
1884
2025
  continue;
1885
2026
  }
1886
2027
  if (span.type === "strikethrough") {
1887
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2028
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1888
2029
  result += `<del>${inner}</del>`;
1889
2030
  continue;
1890
2031
  }
@@ -1905,21 +2046,23 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1905
2046
  result += span.content;
1906
2047
  continue;
1907
2048
  }
1908
- result += escapeHtml(span.content);
2049
+ const content = tokens ? replaceDocumentTokens(span.content, tokens) : span.content;
2050
+ result += escapeHtml(content);
1909
2051
  }
1910
2052
  return result;
1911
2053
  }
1912
- async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
2054
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd(), tokens) {
1913
2055
  let bodyHtml = "";
2056
+ const tokenCtx = tokens || resolved;
1914
2057
  for (const node of nodes) {
1915
2058
  if (node.type === "heading") {
1916
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2059
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1917
2060
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
1918
2061
  `;
1919
2062
  continue;
1920
2063
  }
1921
2064
  if (node.type === "paragraph") {
1922
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2065
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1923
2066
  bodyHtml += ` <p>${inner}</p>
1924
2067
  `;
1925
2068
  continue;
@@ -1934,7 +2077,7 @@ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1934
2077
  const gap = node.columnGap || "1.5rem";
1935
2078
  let colChildrenHtml = "";
1936
2079
  for (const col of node.children || []) {
1937
- const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir);
2080
+ const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir, tokenCtx);
1938
2081
  colChildrenHtml += ` <div class="markforge-col">
1939
2082
  ${colInner} </div>
1940
2083
  `;
@@ -1960,7 +2103,7 @@ ${escapeHtml(node.text || "")}
1960
2103
  continue;
1961
2104
  }
1962
2105
  if (node.type === "callout") {
1963
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2106
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1964
2107
  const CALLOUT_STYLES = {
1965
2108
  NOTE: { bg: "#ECFDFD", border: "#33CDCF", titleColor: "#009DA0" },
1966
2109
  TIP: { bg: "#ecfdf5", border: "#10b981", titleColor: "#10b981" },
@@ -1980,7 +2123,7 @@ ${escapeHtml(node.text || "")}
1980
2123
  continue;
1981
2124
  }
1982
2125
  if (node.type === "blockquote") {
1983
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2126
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1984
2127
  bodyHtml += ` <blockquote>${inner}</blockquote>
1985
2128
  `;
1986
2129
  continue;
@@ -1994,7 +2137,7 @@ ${escapeHtml(node.text || "")}
1994
2137
  for (const cell of row.children || []) {
1995
2138
  const tag = cell.isHeader ? "th" : "td";
1996
2139
  const align = cell.align ? ` align="${cell.align}"` : "";
1997
- const inner = await renderInlinesToHtml(cell.inlines, baseDir);
2140
+ const inner = await renderInlinesToHtml(cell.inlines, baseDir, tokenCtx);
1998
2141
  bodyHtml += ` <${tag}${align}>${inner}</${tag}>
1999
2142
  `;
2000
2143
  }
@@ -2010,7 +2153,7 @@ ${escapeHtml(node.text || "")}
2010
2153
  bodyHtml += ` <${tag}>
2011
2154
  `;
2012
2155
  for (const item of node.children) {
2013
- const inner = await renderInlinesToHtml(item.inlines, baseDir);
2156
+ const inner = await renderInlinesToHtml(item.inlines, baseDir, tokenCtx);
2014
2157
  bodyHtml += ` <li>${inner}</li>
2015
2158
  `;
2016
2159
  }
@@ -2323,8 +2466,8 @@ async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
2323
2466
  `;
2324
2467
  return { html, css };
2325
2468
  }
2326
- async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2327
- var _a;
2469
+ async function buildHtmlDocument(doc, config = {}, baseDir = process.cwd()) {
2470
+ var _a, _b;
2328
2471
  const resolved = resolveDocumentConfig(doc.metadata, config);
2329
2472
  const baseThemeCss = generateThemeCss(resolved.theme);
2330
2473
  let customCss = "";
@@ -2420,6 +2563,9 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2420
2563
  bodyHtml += ` </header>
2421
2564
  `;
2422
2565
  }
2566
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
2567
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
2568
+ }
2423
2569
  if (resolved.toc && doc.tocEntries.length > 0) {
2424
2570
  bodyHtml += ` <nav class="table-of-contents">
2425
2571
  `;
@@ -2435,12 +2581,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2435
2581
  </nav>
2436
2582
  `;
2437
2583
  }
2438
- bodyHtml += await renderNodesToHtml(doc.nodes, resolved, baseDir);
2584
+ const mergedTokens = {
2585
+ ...config.metadata,
2586
+ ...doc.metadata,
2587
+ ...resolved,
2588
+ title: resolved.title,
2589
+ subtitle: resolved.subtitle,
2590
+ author: resolved.author,
2591
+ version: resolved.version,
2592
+ date: resolved.date,
2593
+ company: resolved.company
2594
+ };
2595
+ const nodesHtml = await renderNodesToHtml(doc.nodes, resolved, baseDir, mergedTokens);
2596
+ bodyHtml += ` <main class="markforge-content-body">
2597
+ ${nodesHtml} </main>
2598
+ `;
2439
2599
  let footnotesHtml = "";
2440
2600
  if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
2441
2601
  let fnListHtml = "";
2442
2602
  for (const def of doc.footnoteDefs) {
2443
- const defInner = await renderInlinesToHtml(def.inlines, baseDir);
2603
+ const defInner = await renderInlinesToHtml(def.inlines, baseDir, mergedTokens);
2444
2604
  fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">&#8617;</a></li>
2445
2605
  `;
2446
2606
  }
@@ -2478,17 +2638,7 @@ ${fnListHtml} </ol>
2478
2638
  }
2479
2639
  @media print {
2480
2640
  .document-watermark {
2481
- position: fixed;
2482
- top: 0;
2483
- left: 0;
2484
- right: 0;
2485
- bottom: 0;
2486
- width: 100vw;
2487
- height: 100vh;
2488
- pointer-events: none;
2489
- z-index: 0;
2490
- -webkit-print-color-adjust: exact;
2491
- print-color-adjust: exact;
2641
+ display: none !important;
2492
2642
  }
2493
2643
  }
2494
2644
  `;
@@ -2667,8 +2817,11 @@ function escapeXml(str) {
2667
2817
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2668
2818
  }
2669
2819
  function generateWatermarkPngBuffer(chromePath, wm) {
2670
- const tmpHtml = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
2671
- const tmpPng = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
2820
+ const tmpId = Math.random().toString(36).substring(2, 9);
2821
+ const tmpDir = os.tmpdir();
2822
+ const tmpHtml = path4.join(tmpDir, `markforge-wm-${tmpId}.html`);
2823
+ const tmpPng = path4.join(tmpDir, `markforge-wm-${tmpId}.png`);
2824
+ const tmpProfile = path4.join(tmpDir, `markforge-wm-prof-${tmpId}`);
2672
2825
  try {
2673
2826
  const text = escapeXml(wm.text.toUpperCase());
2674
2827
  const fontSize = (wm.fontSize || 52) * 1.5;
@@ -2719,6 +2872,9 @@ function generateWatermarkPngBuffer(chromePath, wm) {
2719
2872
  chromePath,
2720
2873
  [
2721
2874
  "--headless=new",
2875
+ `--user-data-dir=${tmpProfile}`,
2876
+ "--no-first-run",
2877
+ "--no-default-browser-check",
2722
2878
  "--disable-gpu",
2723
2879
  "--disable-sync",
2724
2880
  "--disable-extensions",
@@ -2740,6 +2896,7 @@ function generateWatermarkPngBuffer(chromePath, wm) {
2740
2896
  try {
2741
2897
  if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
2742
2898
  if (fs4.existsSync(tmpPng)) fs4.unlinkSync(tmpPng);
2899
+ if (fs4.existsSync(tmpProfile)) fs4.rmSync(tmpProfile, { recursive: true, force: true });
2743
2900
  } catch {
2744
2901
  }
2745
2902
  }
@@ -2755,6 +2912,8 @@ function findChromeExecutable() {
2755
2912
  const winLocalAppData = process.env.LOCALAPPDATA ?? "";
2756
2913
  const winProgramFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
2757
2914
  const winProgramFilesX86 = process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
2915
+ const winProgramW6432 = process.env.ProgramW6432 ?? "C:\\Program Files";
2916
+ const winUserProfile = process.env.USERPROFILE ?? "";
2758
2917
  const candidates = [
2759
2918
  // Linux
2760
2919
  "/usr/bin/google-chrome",
@@ -2773,17 +2932,34 @@ function findChromeExecutable() {
2773
2932
  // Windows — Microsoft Edge (Native Windows 10/11 browser, enterprise whitelist friendly)
2774
2933
  `${winProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
2775
2934
  `${winProgramFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
2935
+ `${winProgramW6432}\\Microsoft\\Edge\\Application\\msedge.exe`,
2776
2936
  `${winLocalAppData}\\Microsoft\\Edge\\Application\\msedge.exe`,
2777
- // Windows — Google Chrome
2937
+ `${winLocalAppData}\\Microsoft\\Edge Dev\\Application\\msedge.exe`,
2938
+ `${winLocalAppData}\\Microsoft\\Edge Beta\\Application\\msedge.exe`,
2939
+ // Windows — Google Chrome & Chrome SxS (Canary)
2778
2940
  `${winProgramFiles}\\Google\\Chrome\\Application\\chrome.exe`,
2779
2941
  `${winProgramFilesX86}\\Google\\Chrome\\Application\\chrome.exe`,
2942
+ `${winProgramW6432}\\Google\\Chrome\\Application\\chrome.exe`,
2780
2943
  `${winLocalAppData}\\Google\\Chrome\\Application\\chrome.exe`,
2944
+ `${winLocalAppData}\\Google\\Chrome SxS\\Application\\chrome.exe`,
2781
2945
  // Windows — Brave Browser
2782
2946
  `${winProgramFiles}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
2783
2947
  `${winProgramFilesX86}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
2948
+ `${winProgramW6432}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
2784
2949
  `${winLocalAppData}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
2785
2950
  // Windows — Chromium
2786
- `${winLocalAppData}\\Chromium\\Application\\chrome.exe`
2951
+ `${winLocalAppData}\\Chromium\\Application\\chrome.exe`,
2952
+ // Windows — Scoop Package Manager
2953
+ `${winUserProfile}\\scoop\\apps\\googlechrome\\current\\chrome.exe`,
2954
+ `${winUserProfile}\\scoop\\apps\\chromium\\current\\chrome.exe`,
2955
+ `${winUserProfile}\\scoop\\apps\\brave\\current\\brave.exe`,
2956
+ `${winUserProfile}\\scoop\\apps\\msedge\\current\\msedge.exe`,
2957
+ `${winUserProfile}\\scoop\\shims\\chrome.exe`,
2958
+ `${winUserProfile}\\scoop\\shims\\msedge.exe`,
2959
+ // Windows — Chocolatey
2960
+ "C:\\ProgramData\\chocolatey\\bin\\chrome.exe",
2961
+ "C:\\ProgramData\\chocolatey\\bin\\msedge.exe",
2962
+ "C:\\ProgramData\\chocolatey\\bin\\brave.exe"
2787
2963
  ].filter(Boolean);
2788
2964
  for (const candidate of candidates) {
2789
2965
  try {
@@ -2794,13 +2970,15 @@ function findChromeExecutable() {
2794
2970
  }
2795
2971
  }
2796
2972
  try {
2797
- const cmd = isWin ? "where" : "which";
2798
- const names = isWin ? ["chrome", "msedge", "brave", "google-chrome", "chromium", "chromium-browser"] : ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge", "brave-browser"];
2973
+ const cmd = isWin ? "where.exe" : "which";
2974
+ const names = isWin ? ["chrome.exe", "msedge.exe", "brave.exe", "chrome", "msedge", "brave", "google-chrome", "chromium", "chromium-browser"] : ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge", "brave-browser"];
2799
2975
  for (const name of names) {
2800
- const res = spawnSync(cmd, [name], { encoding: "utf-8" });
2976
+ const res = spawnSync(cmd, [name], { encoding: "utf-8", windowsHide: true });
2801
2977
  if (res.status === 0 && res.stdout.trim()) {
2802
- const binPath = res.stdout.split(/\r?\n/)[0].trim();
2803
- if (fs4.existsSync(binPath)) return binPath;
2978
+ const lines = res.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
2979
+ for (const line of lines) {
2980
+ if (fs4.existsSync(line)) return line;
2981
+ }
2804
2982
  }
2805
2983
  }
2806
2984
  } catch {
@@ -2808,7 +2986,7 @@ function findChromeExecutable() {
2808
2986
  return null;
2809
2987
  }
2810
2988
  function injectPagedMediaStyles(html, config, metadata) {
2811
- var _a, _b, _c, _d, _e, _f, _g, _h;
2989
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2812
2990
  const resolved = resolveDocumentConfig(metadata || {}, config);
2813
2991
  const size = resolved.paperSize;
2814
2992
  const orientation = resolved.orientation;
@@ -2883,9 +3061,11 @@ function injectPagedMediaStyles(html, config, metadata) {
2883
3061
  min-height: 100vh;
2884
3062
  height: 100vh;
2885
3063
  box-sizing: border-box;
3064
+ break-before: page;
3065
+ break-after: avoid;
2886
3066
  }` : "";
2887
- const pagedCss = `
2888
- @page {
3067
+ const tocPageCss = resolved.toc ? `
3068
+ @page toc-page {
2889
3069
  size: ${size} ${orientation};
2890
3070
  margin-top: ${top};
2891
3071
  margin-bottom: ${bottom};
@@ -2896,9 +3076,36 @@ function injectPagedMediaStyles(html, config, metadata) {
2896
3076
  ${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
2897
3077
  ${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
2898
3078
  ${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
2899
- ${buildZoneCss("bottom-right", (_h = resolved.footer) == null ? void 0 : _h.right, true)}
3079
+ @bottom-right {
3080
+ content: counter(page, lower-roman);
3081
+ font-size: 9pt;
3082
+ color: #94a3b8;
3083
+ }
3084
+ }
3085
+ .table-of-contents {
3086
+ page: toc-page;
3087
+ page-break-after: always;
3088
+ break-after: page;
3089
+ }
3090
+ .markforge-content-body {
3091
+ counter-reset: page 1;
3092
+ }` : "";
3093
+ const pagedCss = `
3094
+ @page {
3095
+ size: ${size} ${orientation};
3096
+ margin-top: ${top};
3097
+ margin-bottom: ${bottom};
3098
+ margin-left: ${left};
3099
+ margin-right: ${right};
3100
+ ${buildZoneCss("top-left", (_h = resolved.header) == null ? void 0 : _h.left)}
3101
+ ${buildZoneCss("top-center", (_i = resolved.header) == null ? void 0 : _i.center)}
3102
+ ${buildZoneCss("top-right", (_j = resolved.header) == null ? void 0 : _j.right)}
3103
+ ${buildZoneCss("bottom-left", (_k = resolved.footer) == null ? void 0 : _k.left)}
3104
+ ${buildZoneCss("bottom-center", (_l = resolved.footer) == null ? void 0 : _l.center)}
3105
+ ${buildZoneCss("bottom-right", (_m = resolved.footer) == null ? void 0 : _m.right, true)}
2900
3106
  }
2901
3107
  ${coverPageCss}
3108
+ ${tocPageCss}
2902
3109
  ${backCoverCss}
2903
3110
  @media print {
2904
3111
  body { padding: 0; }
@@ -3029,9 +3236,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
3029
3236
  const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
3030
3237
  if (wmPng) {
3031
3238
  const embeddedPng = await pdfDoc.embedPng(wmPng);
3239
+ const totalPages = pdfDoc.getPageCount();
3032
3240
  const pages = pdfDoc.getPages();
3033
3241
  const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
3034
- const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? pages.length - 1 : pages.length;
3242
+ const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? totalPages - 1 : totalPages;
3035
3243
  for (let i = startPageIndex; i < endPageIndex; i++) {
3036
3244
  const page = pages[i];
3037
3245
  const { width, height } = page.getSize();
@@ -3104,6 +3312,8 @@ async function renderMermaidToPng(mermaidCode, _baseDir = process.cwd()) {
3104
3312
  const tmpDir = os2.tmpdir();
3105
3313
  const tmpHtml = path5.join(tmpDir, `mermaid_${tmpId}.html`);
3106
3314
  const tmpScreenshot = path5.join(tmpDir, `mermaid_${tmpId}.png`);
3315
+ const tmpProfile = path5.join(tmpDir, `mermaid_prof_${tmpId}`);
3316
+ const isWin = process.platform === "win32";
3107
3317
  const htmlContent = `<!DOCTYPE html>
3108
3318
  <html>
3109
3319
  <head>
@@ -3147,10 +3357,14 @@ ${mermaidCode}
3147
3357
  const res = spawnSync2(
3148
3358
  chromePath,
3149
3359
  [
3150
- "--headless",
3360
+ "--headless=new",
3361
+ `--user-data-dir=${tmpProfile}`,
3362
+ "--no-first-run",
3363
+ "--no-default-browser-check",
3151
3364
  "--disable-gpu",
3152
- "--no-sandbox",
3153
- "--disable-setuid-sandbox",
3365
+ "--disable-sync",
3366
+ "--disable-extensions",
3367
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
3154
3368
  "--allow-file-access-from-files",
3155
3369
  "--disable-web-security",
3156
3370
  "--disable-software-rasterizer",
@@ -3159,7 +3373,7 @@ ${mermaidCode}
3159
3373
  `--screenshot=${tmpScreenshot}`,
3160
3374
  fileUrl
3161
3375
  ],
3162
- { timeout: 15e3 }
3376
+ { timeout: 15e3, windowsHide: true }
3163
3377
  );
3164
3378
  if (res.status === 0 && fs5.existsSync(tmpScreenshot)) {
3165
3379
  const buffer = fs5.readFileSync(tmpScreenshot);
@@ -3170,6 +3384,7 @@ ${mermaidCode}
3170
3384
  try {
3171
3385
  if (fs5.existsSync(tmpHtml)) fs5.unlinkSync(tmpHtml);
3172
3386
  if (fs5.existsSync(tmpScreenshot)) fs5.unlinkSync(tmpScreenshot);
3387
+ if (fs5.existsSync(tmpProfile)) fs5.rmSync(tmpProfile, { recursive: true, force: true });
3173
3388
  } catch {
3174
3389
  }
3175
3390
  }
@@ -3371,7 +3586,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
3371
3586
  return runs;
3372
3587
  }
3373
3588
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3374
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
3589
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
3375
3590
  const resolved = resolveDocumentConfig(doc.metadata, config);
3376
3591
  const docElements = [];
3377
3592
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -3443,6 +3658,9 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3443
3658
  );
3444
3659
  }
3445
3660
  }
3661
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
3662
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
3663
+ }
3446
3664
  if (resolved.toc) {
3447
3665
  const headingNodes = doc.nodes.filter(
3448
3666
  (n) => n.type === "heading" && typeof n.level === "number" && n.level >= 1 && n.level <= 3
@@ -3512,7 +3730,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3512
3730
  ]
3513
3731
  });
3514
3732
  docElements.push(tocCard);
3515
- docElements.push(new Paragraph({ spacing: { after: 200 } }));
3733
+ docElements.push(
3734
+ new Paragraph({
3735
+ children: [new PageBreak()]
3736
+ })
3737
+ );
3516
3738
  }
3517
3739
  }
3518
3740
  for (const node of doc.nodes) {
@@ -3522,7 +3744,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3522
3744
  font: defaultFont,
3523
3745
  size: 34,
3524
3746
  // 17pt
3525
- color: textHex,
3747
+ color: primaryDarkHex,
3526
3748
  bold: true
3527
3749
  });
3528
3750
  docElements.push(
@@ -3568,7 +3790,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3568
3790
  font: defaultFont,
3569
3791
  size: 22,
3570
3792
  // 11pt
3571
- color: textHex,
3793
+ color: primaryDarkHex,
3572
3794
  bold: true
3573
3795
  });
3574
3796
  docElements.push(
@@ -3798,7 +4020,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3798
4020
  }
3799
4021
  if (node.type === "table" && node.children) {
3800
4022
  const tableRows = [];
3801
- const numCols = ((_c = (_b = node.children[0]) == null ? void 0 : _b.children) == null ? void 0 : _c.length) || 1;
4023
+ const numCols = ((_d = (_c = node.children[0]) == null ? void 0 : _c.children) == null ? void 0 : _d.length) || 1;
3802
4024
  const colWidth = Math.floor(9e3 / numCols);
3803
4025
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
3804
4026
  const rowNode = node.children[rowIdx];
@@ -3808,7 +4030,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3808
4030
  if (rowNode.children) {
3809
4031
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
3810
4032
  const cellNode = rowNode.children[colIdx];
3811
- const align = (_d = node.align) == null ? void 0 : _d[colIdx];
4033
+ const align = (_e = node.align) == null ? void 0 : _e[colIdx];
3812
4034
  let alignment = AlignmentType.LEFT;
3813
4035
  if (align === "center") alignment = AlignmentType.CENTER;
3814
4036
  if (align === "right") alignment = AlignmentType.RIGHT;
@@ -4115,7 +4337,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4115
4337
  const centerPos = Math.round(contentWidthTwip / 2);
4116
4338
  const rightPos = contentWidthTwip;
4117
4339
  const headerRuns = [];
4118
- if ((_e = resolved.header) == null ? void 0 : _e.left) {
4340
+ if ((_f = resolved.header) == null ? void 0 : _f.left) {
4119
4341
  headerRuns.push(
4120
4342
  new TextRun({
4121
4343
  text: resolved.header.left.text,
@@ -4128,7 +4350,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4128
4350
  );
4129
4351
  }
4130
4352
  headerRuns.push(new TextRun({ text: " " }));
4131
- if ((_f = resolved.header) == null ? void 0 : _f.center) {
4353
+ if ((_g = resolved.header) == null ? void 0 : _g.center) {
4132
4354
  headerRuns.push(
4133
4355
  new TextRun({
4134
4356
  text: resolved.header.center.text,
@@ -4141,7 +4363,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4141
4363
  );
4142
4364
  }
4143
4365
  headerRuns.push(new TextRun({ text: " " }));
4144
- if ((_g = resolved.header) == null ? void 0 : _g.right) {
4366
+ if ((_h = resolved.header) == null ? void 0 : _h.right) {
4145
4367
  headerRuns.push(
4146
4368
  new TextRun({
4147
4369
  text: resolved.header.right.text,
@@ -4180,7 +4402,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4180
4402
  ]
4181
4403
  }) : void 0;
4182
4404
  const footerRuns = [];
4183
- if ((_h = resolved.footer) == null ? void 0 : _h.left) {
4405
+ if ((_i = resolved.footer) == null ? void 0 : _i.left) {
4184
4406
  footerRuns.push(
4185
4407
  new TextRun({
4186
4408
  text: resolved.footer.left.text,
@@ -4193,7 +4415,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4193
4415
  );
4194
4416
  }
4195
4417
  footerRuns.push(new TextRun({ text: " " }));
4196
- if ((_i = resolved.footer) == null ? void 0 : _i.center) {
4418
+ if ((_j = resolved.footer) == null ? void 0 : _j.center) {
4197
4419
  footerRuns.push(
4198
4420
  new TextRun({
4199
4421
  text: resolved.footer.center.text,
@@ -4206,7 +4428,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4206
4428
  );
4207
4429
  }
4208
4430
  footerRuns.push(new TextRun({ text: " " }));
4209
- if ((_j = resolved.footer) == null ? void 0 : _j.right) {
4431
+ if ((_k = resolved.footer) == null ? void 0 : _k.right) {
4210
4432
  const rZone = resolved.footer.right;
4211
4433
  const rColor = rZone.color.replace("#", "");
4212
4434
  const rSize = (rZone.fontSize || 9) * 2;
@@ -4304,6 +4526,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4304
4526
  );
4305
4527
  docSections.push({
4306
4528
  properties: {
4529
+ type: SectionType.NEXT_PAGE,
4307
4530
  page: {
4308
4531
  size: {
4309
4532
  width: resolved.paperDimensions.widthTwip,
@@ -4318,14 +4541,19 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4318
4541
  }
4319
4542
  }
4320
4543
  },
4321
- headers: void 0,
4322
- footers: void 0,
4544
+ headers: { default: new Header({ children: [] }) },
4545
+ footers: { default: new Footer({ children: [] }) },
4323
4546
  children: coverElements
4324
4547
  });
4325
4548
  }
4326
4549
  docSections.push({
4327
4550
  properties: {
4551
+ type: SectionType.NEXT_PAGE,
4328
4552
  page: {
4553
+ pageNumbers: {
4554
+ start: 1,
4555
+ formatType: NumberFormat.DECIMAL
4556
+ },
4329
4557
  size: {
4330
4558
  width: resolved.paperDimensions.widthTwip,
4331
4559
  height: resolved.paperDimensions.heightTwip,
@@ -4341,8 +4569,8 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4341
4569
  }
4342
4570
  }
4343
4571
  },
4344
- headers: docHeader ? { default: docHeader } : void 0,
4345
- footers: docFooter ? { default: docFooter } : void 0,
4572
+ headers: docHeader ? { default: docHeader } : { default: new Header({ children: [] }) },
4573
+ footers: docFooter ? { default: docFooter } : { default: new Footer({ children: [] }) },
4346
4574
  children: docElements
4347
4575
  });
4348
4576
  if (resolved.backCover && resolved.backCover.enabled) {
@@ -4357,6 +4585,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4357
4585
  );
4358
4586
  docSections.push({
4359
4587
  properties: {
4588
+ type: SectionType.NEXT_PAGE,
4360
4589
  page: {
4361
4590
  size: {
4362
4591
  width: resolved.paperDimensions.widthTwip,
@@ -4371,8 +4600,8 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4371
4600
  }
4372
4601
  }
4373
4602
  },
4374
- headers: void 0,
4375
- footers: void 0,
4603
+ headers: { default: new Header({ children: [] }) },
4604
+ footers: { default: new Footer({ children: [] }) },
4376
4605
  children: backElements
4377
4606
  });
4378
4607
  }
@@ -4775,6 +5004,432 @@ function createEmptyDocxCell(widthDxa) {
4775
5004
  });
4776
5005
  }
4777
5006
 
5007
+ // src/core/text/textBuilder.ts
5008
+ var LINE_WIDTH = 80;
5009
+ var HR_DOUBLE = "=".repeat(LINE_WIDTH);
5010
+ var HR_SINGLE = "-".repeat(LINE_WIDTH);
5011
+ function centerText(text, width = LINE_WIDTH) {
5012
+ if (text.length >= width) return text;
5013
+ const leftPad = Math.floor((width - text.length) / 2);
5014
+ return " ".repeat(leftPad) + text;
5015
+ }
5016
+ function renderInlinesToText(spans = [], meta = {}) {
5017
+ let result = "";
5018
+ for (const span of spans) {
5019
+ switch (span.type) {
5020
+ case "text": {
5021
+ const content = replaceDocumentTokens(span.content, meta);
5022
+ result += content;
5023
+ break;
5024
+ }
5025
+ case "bold": {
5026
+ const inner = span.children ? renderInlinesToText(span.children, meta) : replaceDocumentTokens(span.content, meta);
5027
+ result += `**${inner}**`;
5028
+ break;
5029
+ }
5030
+ case "italic": {
5031
+ const inner = span.children ? renderInlinesToText(span.children, meta) : replaceDocumentTokens(span.content, meta);
5032
+ result += `*${inner}*`;
5033
+ break;
5034
+ }
5035
+ case "code": {
5036
+ result += `\`${span.content}\``;
5037
+ break;
5038
+ }
5039
+ case "link": {
5040
+ const inner = span.children ? renderInlinesToText(span.children, meta) : replaceDocumentTokens(span.content, meta);
5041
+ result += span.url && span.url !== inner ? `${inner} (${span.url})` : inner;
5042
+ break;
5043
+ }
5044
+ case "image": {
5045
+ result += `[Image: ${span.alt || "image"} (${span.url || ""})]`;
5046
+ break;
5047
+ }
5048
+ case "strikethrough": {
5049
+ const inner = span.children ? renderInlinesToText(span.children, meta) : replaceDocumentTokens(span.content, meta);
5050
+ result += `~${inner}~`;
5051
+ break;
5052
+ }
5053
+ case "mathInline": {
5054
+ result += `$${span.content}$`;
5055
+ break;
5056
+ }
5057
+ case "footnoteRef": {
5058
+ const id = span.footnoteId || span.content;
5059
+ result += `[${id}]`;
5060
+ break;
5061
+ }
5062
+ case "htmlInline": {
5063
+ result += span.content.replace(/<[^>]+>/g, "");
5064
+ break;
5065
+ }
5066
+ default: {
5067
+ result += span.content || "";
5068
+ break;
5069
+ }
5070
+ }
5071
+ }
5072
+ return result;
5073
+ }
5074
+ async function buildTextDocument(doc, config = {}, _baseDir = process.cwd()) {
5075
+ const resolved = resolveDocumentConfig(doc.metadata, config);
5076
+ const meta = {
5077
+ ...doc.metadata,
5078
+ ...config.metadata,
5079
+ title: resolved.title,
5080
+ subtitle: resolved.subtitle,
5081
+ author: resolved.author,
5082
+ company: resolved.company,
5083
+ version: resolved.version,
5084
+ date: resolved.date
5085
+ };
5086
+ const nodes = JSON.parse(JSON.stringify(doc.nodes));
5087
+ const tocEntries = JSON.parse(JSON.stringify(doc.tocEntries));
5088
+ if (resolved.numberHeadings && resolved.numberHeadings.enabled) {
5089
+ applyHeadingNumbering(nodes, tocEntries, resolved.numberHeadings);
5090
+ }
5091
+ const lines = [];
5092
+ if (resolved.coverPage && resolved.coverPage.enabled) {
5093
+ const cover = resolved.coverPage;
5094
+ const coverTitle = (cover.title || meta.title || "DOCUMENT").toUpperCase();
5095
+ const coverSubtitle = cover.subtitle || meta.subtitle || "";
5096
+ const coverAuthor = cover.author || meta.author || "";
5097
+ const coverCompany = cover.company || meta.company || "";
5098
+ const coverDate = cover.date || meta.date || "";
5099
+ const coverVersion = cover.version || meta.version || "";
5100
+ const coverBadge = cover.badge || "";
5101
+ const coverFooter = cover.footerText || "";
5102
+ lines.push(HR_DOUBLE);
5103
+ lines.push(centerText(coverTitle));
5104
+ if (coverSubtitle) {
5105
+ lines.push(centerText(coverSubtitle));
5106
+ }
5107
+ lines.push(HR_DOUBLE);
5108
+ if (coverBadge) {
5109
+ lines.push(`[${coverBadge}]`);
5110
+ lines.push("");
5111
+ }
5112
+ if (coverAuthor) lines.push(`Author: ${coverAuthor}`);
5113
+ if (coverCompany) lines.push(`Company: ${coverCompany}`);
5114
+ if (coverDate) lines.push(`Date: ${coverDate}`);
5115
+ if (coverVersion) lines.push(`Version: ${coverVersion}`);
5116
+ if (coverFooter) lines.push(`Notice: ${coverFooter}`);
5117
+ lines.push(HR_DOUBLE);
5118
+ lines.push("");
5119
+ lines.push("");
5120
+ }
5121
+ if (resolved.toc) {
5122
+ const headings = nodes.filter(
5123
+ (n) => n.type === "heading" && (n.level || 1) <= 3
5124
+ );
5125
+ if (headings.length > 0) {
5126
+ const tocTitle = "TABLE OF CONTENTS";
5127
+ lines.push(HR_DOUBLE);
5128
+ lines.push(centerText(tocTitle));
5129
+ lines.push(HR_DOUBLE);
5130
+ for (const h of headings) {
5131
+ const level = h.level || 1;
5132
+ const indent = " ".repeat(level - 1);
5133
+ const headingText = renderInlinesToText(h.inlines, meta);
5134
+ const prefix = level === 1 ? "* " : "- ";
5135
+ lines.push(`${indent}${prefix}${headingText}`);
5136
+ }
5137
+ lines.push(HR_DOUBLE);
5138
+ lines.push("");
5139
+ lines.push("");
5140
+ }
5141
+ }
5142
+ function renderNode(node, listLevel = 0) {
5143
+ var _a;
5144
+ switch (node.type) {
5145
+ case "heading": {
5146
+ const level = node.level || 1;
5147
+ const text = renderInlinesToText(node.inlines, meta);
5148
+ lines.push("");
5149
+ if (level === 1) {
5150
+ lines.push(HR_DOUBLE);
5151
+ lines.push(text.toUpperCase());
5152
+ lines.push(HR_DOUBLE);
5153
+ } else if (level === 2) {
5154
+ lines.push(HR_SINGLE);
5155
+ lines.push(text);
5156
+ lines.push(HR_SINGLE);
5157
+ } else {
5158
+ const hashes = "#".repeat(level);
5159
+ lines.push(`${hashes} ${text}`);
5160
+ }
5161
+ lines.push("");
5162
+ break;
5163
+ }
5164
+ case "paragraph": {
5165
+ const text = renderInlinesToText(node.inlines, meta);
5166
+ if (text.trim()) {
5167
+ lines.push(text);
5168
+ lines.push("");
5169
+ }
5170
+ break;
5171
+ }
5172
+ case "callout": {
5173
+ const calloutType = (node.calloutType || "NOTE").toUpperCase();
5174
+ lines.push(`| [${calloutType}]`);
5175
+ if (node.inlines && node.inlines.length > 0) {
5176
+ const text = renderInlinesToText(node.inlines, meta);
5177
+ text.split("\n").forEach((l) => lines.push(`| ${l}`));
5178
+ }
5179
+ lines.push("");
5180
+ break;
5181
+ }
5182
+ case "blockquote": {
5183
+ if (node.inlines && node.inlines.length > 0) {
5184
+ const text = renderInlinesToText(node.inlines, meta);
5185
+ text.split("\n").forEach((l) => lines.push(`> ${l}`));
5186
+ lines.push("");
5187
+ }
5188
+ break;
5189
+ }
5190
+ case "codeBlock": {
5191
+ const lang = node.language ? `[Language: ${node.language}]` : "[Code]";
5192
+ lines.push(HR_SINGLE);
5193
+ lines.push(lang);
5194
+ lines.push(HR_SINGLE);
5195
+ const codeText = node.text || "";
5196
+ lines.push(codeText);
5197
+ lines.push(HR_SINGLE);
5198
+ lines.push("");
5199
+ break;
5200
+ }
5201
+ case "list": {
5202
+ if (node.children) {
5203
+ let itemIndex = 1;
5204
+ for (const item of node.children) {
5205
+ const indent = " ".repeat(listLevel);
5206
+ let prefix = node.ordered ? `${itemIndex}. ` : "* ";
5207
+ itemIndex++;
5208
+ if (item.checked !== void 0) {
5209
+ prefix = item.checked ? "[x] " : "[ ] ";
5210
+ }
5211
+ if (item.inlines && item.inlines.length > 0) {
5212
+ const text = renderInlinesToText(item.inlines, meta);
5213
+ lines.push(`${indent}${prefix}${text}`);
5214
+ }
5215
+ if (item.children && item.children.length > 0) {
5216
+ for (const subChild of item.children) {
5217
+ if (subChild.type === "list") {
5218
+ renderNode(subChild, listLevel + 1);
5219
+ } else if (subChild.type === "paragraph") {
5220
+ const text = renderInlinesToText(subChild.inlines, meta);
5221
+ lines.push(`${indent} ${text}`);
5222
+ } else {
5223
+ renderNode(subChild, listLevel + 1);
5224
+ }
5225
+ }
5226
+ }
5227
+ }
5228
+ lines.push("");
5229
+ }
5230
+ break;
5231
+ }
5232
+ case "table": {
5233
+ const rows = node.children || [];
5234
+ if (rows.length === 0) break;
5235
+ const rowCells = [];
5236
+ let maxCols = 0;
5237
+ for (const row of rows) {
5238
+ const cells = row.children || [];
5239
+ const texts = cells.map((c) => renderInlinesToText(c.inlines, meta));
5240
+ const isHeader = cells.some((c) => c.isHeader);
5241
+ maxCols = Math.max(maxCols, texts.length);
5242
+ rowCells.push({ isHeader, texts });
5243
+ }
5244
+ if (maxCols === 0) break;
5245
+ const colWidths = new Array(maxCols).fill(3);
5246
+ for (const r of rowCells) {
5247
+ for (let col = 0; col < maxCols; col++) {
5248
+ const cellText = r.texts[col] || "";
5249
+ colWidths[col] = Math.max(colWidths[col] ?? 3, cellText.length);
5250
+ }
5251
+ }
5252
+ const separatorLine = "+" + colWidths.map((w) => "-".repeat(w + 2)).join("+") + "+";
5253
+ const formatRow = (texts) => {
5254
+ const paddedCells = colWidths.map((width, colIdx) => {
5255
+ const text = texts[colIdx] || "";
5256
+ return " " + text.padEnd(width, " ") + " ";
5257
+ });
5258
+ return "|" + paddedCells.join("|") + "|";
5259
+ };
5260
+ lines.push(separatorLine);
5261
+ for (const r of rowCells) {
5262
+ lines.push(formatRow(r.texts));
5263
+ if (r.isHeader) {
5264
+ lines.push(separatorLine);
5265
+ }
5266
+ }
5267
+ if (!((_a = rowCells[rowCells.length - 1]) == null ? void 0 : _a.isHeader)) {
5268
+ lines.push(separatorLine);
5269
+ }
5270
+ lines.push("");
5271
+ break;
5272
+ }
5273
+ case "mathBlock": {
5274
+ lines.push("[Equation]");
5275
+ lines.push(` $$${node.text || ""} $$`);
5276
+ lines.push("");
5277
+ break;
5278
+ }
5279
+ case "mermaid": {
5280
+ lines.push("[Diagram: Mermaid]");
5281
+ lines.push(node.text || "");
5282
+ lines.push("");
5283
+ break;
5284
+ }
5285
+ case "thematicBreak": {
5286
+ lines.push(HR_SINGLE);
5287
+ lines.push("");
5288
+ break;
5289
+ }
5290
+ case "columns": {
5291
+ if (node.children) {
5292
+ for (const col of node.children) {
5293
+ if (col.children) {
5294
+ for (const child of col.children) {
5295
+ renderNode(child, listLevel);
5296
+ }
5297
+ }
5298
+ }
5299
+ }
5300
+ break;
5301
+ }
5302
+ default: {
5303
+ if (node.children && node.children.length > 0) {
5304
+ for (const child of node.children) {
5305
+ renderNode(child, listLevel);
5306
+ }
5307
+ }
5308
+ break;
5309
+ }
5310
+ }
5311
+ }
5312
+ for (const node of nodes) {
5313
+ renderNode(node);
5314
+ }
5315
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
5316
+ lines.push(HR_SINGLE);
5317
+ lines.push("FOOTNOTES");
5318
+ lines.push(HR_SINGLE);
5319
+ for (const fn of doc.footnoteDefs) {
5320
+ const text = renderInlinesToText(fn.inlines, meta);
5321
+ lines.push(`[${fn.id}] ${text}`);
5322
+ }
5323
+ lines.push("");
5324
+ }
5325
+ if (resolved.signatures && resolved.signatures.items && resolved.signatures.items.length > 0) {
5326
+ lines.push(HR_SINGLE);
5327
+ lines.push("SIGNATURES & APPROVALS");
5328
+ lines.push(HR_SINGLE);
5329
+ lines.push("");
5330
+ for (const item of resolved.signatures.items) {
5331
+ const title = item.title || "Signatory";
5332
+ const name = item.name ? replaceDocumentTokens(item.name, meta) : "";
5333
+ const role = item.role ? replaceDocumentTokens(item.role, meta) : "";
5334
+ const date = item.date ? replaceDocumentTokens(item.date, meta) : "";
5335
+ lines.push(`[${title}]`);
5336
+ lines.push("____________________________________");
5337
+ if (name) lines.push(`Name: ${name}`);
5338
+ if (role) lines.push(`Role: ${role}`);
5339
+ if (date) lines.push(`Date: ${date}`);
5340
+ lines.push("");
5341
+ }
5342
+ }
5343
+ if (resolved.backCover && resolved.backCover.enabled) {
5344
+ const back = resolved.backCover;
5345
+ const backTitle = (back.title || "THANK YOU").toUpperCase();
5346
+ const backSubtitle = back.subtitle || "";
5347
+ const backCompany = back.company ? replaceDocumentTokens(back.company, meta) : "";
5348
+ const backAddress = back.address ? replaceDocumentTokens(back.address, meta) : "";
5349
+ const backEmail = back.email ? replaceDocumentTokens(back.email, meta) : "";
5350
+ const backPhone = back.phone ? replaceDocumentTokens(back.phone, meta) : "";
5351
+ const backWebsite = back.website ? replaceDocumentTokens(back.website, meta) : "";
5352
+ const backCopyright = back.copyright ? replaceDocumentTokens(back.copyright, meta) : "";
5353
+ lines.push(HR_DOUBLE);
5354
+ lines.push(centerText(backTitle));
5355
+ if (backSubtitle) {
5356
+ lines.push(centerText(backSubtitle));
5357
+ }
5358
+ lines.push(HR_DOUBLE);
5359
+ if (backCompany) lines.push(`Company: ${backCompany}`);
5360
+ if (backAddress) lines.push(`Address: ${backAddress}`);
5361
+ if (backEmail) lines.push(`Email: ${backEmail}`);
5362
+ if (backPhone) lines.push(`Phone: ${backPhone}`);
5363
+ if (backWebsite) lines.push(`Website: ${backWebsite}`);
5364
+ if (back.social && back.social.github) {
5365
+ lines.push(`GitHub: ${back.social.github}`);
5366
+ }
5367
+ if (backCopyright) {
5368
+ lines.push("");
5369
+ lines.push(backCopyright);
5370
+ }
5371
+ lines.push(HR_DOUBLE);
5372
+ lines.push("");
5373
+ }
5374
+ return lines.join("\n").trim() + "\n";
5375
+ }
5376
+
5377
+ // src/core/png/pngBuilder.ts
5378
+ import * as fs6 from "fs";
5379
+ import * as path6 from "path";
5380
+ import * as os3 from "os";
5381
+ import { pathToFileURL as pathToFileURL4 } from "url";
5382
+ import { spawnSync as spawnSync3 } from "child_process";
5383
+ async function buildPngDocument(doc, config, baseDir) {
5384
+ const htmlContent = await buildHtmlDocument(doc, config, baseDir);
5385
+ const chromePath = findChromeExecutable();
5386
+ if (!chromePath) {
5387
+ throw new Error(
5388
+ "Headless Chrome / Chromium / Edge executable not found for PNG document export. Please install Google Chrome, Chromium, or Microsoft Edge, or set CHROME_PATH environment variable."
5389
+ );
5390
+ }
5391
+ const tmpHtml = path6.join(
5392
+ os3.tmpdir(),
5393
+ `markforge-png-${Date.now()}-${Math.random().toString(36).slice(2)}.html`
5394
+ );
5395
+ const tmpPng = path6.join(
5396
+ os3.tmpdir(),
5397
+ `markforge-png-${Date.now()}-${Math.random().toString(36).slice(2)}.png`
5398
+ );
5399
+ try {
5400
+ fs6.writeFileSync(tmpHtml, htmlContent, "utf-8");
5401
+ const fileUrl = pathToFileURL4(tmpHtml).href;
5402
+ const spawnResult = spawnSync3(
5403
+ chromePath,
5404
+ [
5405
+ "--headless=new",
5406
+ "--disable-gpu",
5407
+ "--no-sandbox",
5408
+ "--disable-setuid-sandbox",
5409
+ "--hide-scrollbars",
5410
+ "--force-device-scale-factor=2",
5411
+ "--window-size=1200,1600",
5412
+ `--screenshot=${tmpPng}`,
5413
+ fileUrl
5414
+ ],
5415
+ { timeout: 3e4, windowsHide: true }
5416
+ );
5417
+ if (spawnResult.error) {
5418
+ throw new Error(`Failed to execute Chromium for PNG export: ${spawnResult.error.message}`);
5419
+ }
5420
+ if (!fs6.existsSync(tmpPng) || fs6.statSync(tmpPng).size === 0) {
5421
+ throw new Error("Chromium PNG export failed to generate output screenshot file.");
5422
+ }
5423
+ return fs6.readFileSync(tmpPng);
5424
+ } finally {
5425
+ try {
5426
+ if (fs6.existsSync(tmpHtml)) fs6.unlinkSync(tmpHtml);
5427
+ if (fs6.existsSync(tmpPng)) fs6.unlinkSync(tmpPng);
5428
+ } catch {
5429
+ }
5430
+ }
5431
+ }
5432
+
4778
5433
  // src/core/engine.ts
4779
5434
  function formatServerTimestamp(date = /* @__PURE__ */ new Date()) {
4780
5435
  const pad = (n) => String(n).padStart(2, "0");
@@ -4798,20 +5453,20 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
4798
5453
  let baseDir = process.cwd();
4799
5454
  let inputFileName = "document.md";
4800
5455
  let isFilePath = false;
4801
- if (fs6.existsSync(inputFilePathOrContent)) {
5456
+ if (fs7.existsSync(inputFilePathOrContent)) {
4802
5457
  isFilePath = true;
4803
- rawMarkdown = fs6.readFileSync(inputFilePathOrContent, "utf-8");
4804
- baseDir = path6.dirname(path6.resolve(inputFilePathOrContent));
4805
- inputFileName = path6.basename(inputFilePathOrContent);
5458
+ rawMarkdown = fs7.readFileSync(inputFilePathOrContent, "utf-8");
5459
+ baseDir = path7.dirname(path7.resolve(inputFilePathOrContent));
5460
+ inputFileName = path7.basename(inputFilePathOrContent);
4806
5461
  } else {
4807
5462
  rawMarkdown = inputFilePathOrContent;
4808
5463
  }
4809
5464
  onProgress == null ? void 0 : onProgress(`Parsing markdown AST: ${inputFileName}...`);
4810
5465
  const parsedDoc = parseMarkdownDocument(rawMarkdown);
4811
5466
  const baseName = inputFileName.replace(/\.(md|mdx|markdown)$/i, "");
4812
- const outputDir = config.outputDir ? path6.isAbsolute(config.outputDir) ? config.outputDir : path6.resolve(process.cwd(), config.outputDir) : baseDir;
4813
- if (!fs6.existsSync(outputDir)) {
4814
- fs6.mkdirSync(outputDir, { recursive: true });
5467
+ const outputDir = config.outputDir ? path7.isAbsolute(config.outputDir) ? config.outputDir : path7.resolve(process.cwd(), config.outputDir) : baseDir;
5468
+ if (!fs7.existsSync(outputDir)) {
5469
+ fs7.mkdirSync(outputDir, { recursive: true });
4815
5470
  }
4816
5471
  const formats = Array.isArray(config.to) ? config.to : [config.to || "docx", "pdf"];
4817
5472
  const generatedFiles = [];
@@ -4821,8 +5476,8 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
4821
5476
  if (fmt === "docx") {
4822
5477
  onProgress == null ? void 0 : onProgress(`Generating DOCX document: ${baseName}.docx...`);
4823
5478
  const docxBuffer = await buildDocxDocument(parsedDoc, config, baseDir);
4824
- const docxPath = path6.join(outputDir, `${baseName}.docx`);
4825
- fs6.writeFileSync(docxPath, docxBuffer);
5479
+ const docxPath = path7.join(outputDir, `${baseName}.docx`);
5480
+ fs7.writeFileSync(docxPath, docxBuffer);
4826
5481
  generatedFiles.push({
4827
5482
  format: "docx",
4828
5483
  filePath: docxPath,
@@ -4832,8 +5487,8 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
4832
5487
  } else if (fmt === "html") {
4833
5488
  onProgress == null ? void 0 : onProgress(`Generating HTML document: ${baseName}.html...`);
4834
5489
  const htmlString = await buildHtmlDocument(parsedDoc, config, baseDir);
4835
- const htmlPath = path6.join(outputDir, `${baseName}.html`);
4836
- fs6.writeFileSync(htmlPath, htmlString, "utf-8");
5490
+ const htmlPath = path7.join(outputDir, `${baseName}.html`);
5491
+ fs7.writeFileSync(htmlPath, htmlString, "utf-8");
4837
5492
  generatedFiles.push({
4838
5493
  format: "html",
4839
5494
  filePath: htmlPath,
@@ -4843,14 +5498,36 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
4843
5498
  } else if (fmt === "pdf") {
4844
5499
  onProgress == null ? void 0 : onProgress(`Generating PDF document: ${baseName}.pdf...`);
4845
5500
  const pdfBuffer = await buildPdfDocument(parsedDoc, config, baseDir);
4846
- const pdfPath = path6.join(outputDir, `${baseName}.pdf`);
4847
- fs6.writeFileSync(pdfPath, pdfBuffer);
5501
+ const pdfPath = path7.join(outputDir, `${baseName}.pdf`);
5502
+ fs7.writeFileSync(pdfPath, pdfBuffer);
4848
5503
  generatedFiles.push({
4849
5504
  format: "pdf",
4850
5505
  filePath: pdfPath,
4851
5506
  fileName: `${baseName}.pdf`,
4852
5507
  sizeBytes: pdfBuffer.length
4853
5508
  });
5509
+ } else if (fmt === "txt") {
5510
+ onProgress == null ? void 0 : onProgress(`Generating Plain Text document: ${baseName}.txt...`);
5511
+ const textContent = await buildTextDocument(parsedDoc, config, baseDir);
5512
+ const textPath = path7.join(outputDir, `${baseName}.txt`);
5513
+ fs7.writeFileSync(textPath, textContent, "utf-8");
5514
+ generatedFiles.push({
5515
+ format: "txt",
5516
+ filePath: textPath,
5517
+ fileName: `${baseName}.txt`,
5518
+ sizeBytes: Buffer.byteLength(textContent, "utf-8")
5519
+ });
5520
+ } else if (fmt === "png") {
5521
+ onProgress == null ? void 0 : onProgress(`Generating PNG document: ${baseName}.png...`);
5522
+ const pngBuffer = await buildPngDocument(parsedDoc, config, baseDir);
5523
+ const pngPath = path7.join(outputDir, `${baseName}.png`);
5524
+ fs7.writeFileSync(pngPath, pngBuffer);
5525
+ generatedFiles.push({
5526
+ format: "png",
5527
+ filePath: pngPath,
5528
+ fileName: `${baseName}.png`,
5529
+ sizeBytes: pngBuffer.length
5530
+ });
4854
5531
  }
4855
5532
  } catch (err) {
4856
5533
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -4869,14 +5546,14 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
4869
5546
 
4870
5547
  // src/server/previewServer.ts
4871
5548
  import * as http from "http";
4872
- import * as fs7 from "fs";
4873
- import * as path7 from "path";
5549
+ import * as fs8 from "fs";
5550
+ import * as path8 from "path";
4874
5551
  async function startPreviewServer(options) {
4875
- const absoluteFilePath = path7.resolve(process.cwd(), options.filePath);
4876
- if (!fs7.existsSync(absoluteFilePath)) {
5552
+ const absoluteFilePath = path8.resolve(process.cwd(), options.filePath);
5553
+ if (!fs8.existsSync(absoluteFilePath)) {
4877
5554
  throw new Error(`MarkForge preview error: File not found at "${absoluteFilePath}"`);
4878
5555
  }
4879
- const baseDir = path7.dirname(absoluteFilePath);
5556
+ const baseDir = path8.dirname(absoluteFilePath);
4880
5557
  const { config: fileConfig } = await loadConfig(void 0, baseDir);
4881
5558
  const baseConfig = options.config || fileConfig;
4882
5559
  const port = options.port || 3e3;
@@ -4894,9 +5571,9 @@ data: ${Date.now()}
4894
5571
  });
4895
5572
  };
4896
5573
  let debounceTimer = null;
4897
- const watcher = fs7.watch(baseDir, { recursive: false }, (_event, filename) => {
5574
+ const watcher = fs8.watch(baseDir, { recursive: false }, (_event, filename) => {
4898
5575
  if (!filename) return;
4899
- const changedPath = path7.resolve(baseDir, filename);
5576
+ const changedPath = path8.resolve(baseDir, filename);
4900
5577
  if (changedPath === absoluteFilePath || filename.includes("markforge") || filename.endsWith(".css")) {
4901
5578
  if (debounceTimer) clearTimeout(debounceTimer);
4902
5579
  debounceTimer = setTimeout(() => {
@@ -4923,12 +5600,12 @@ data: ${Date.now()}
4923
5600
  }
4924
5601
  if (url.pathname === "/api/file-content" && req.method === "GET") {
4925
5602
  try {
4926
- const content = fs7.readFileSync(absoluteFilePath, "utf-8");
5603
+ const content = fs8.readFileSync(absoluteFilePath, "utf-8");
4927
5604
  res.writeHead(200, { "Content-Type": "application/json" });
4928
5605
  res.end(
4929
5606
  JSON.stringify({
4930
5607
  content,
4931
- fileName: path7.basename(absoluteFilePath),
5608
+ fileName: path8.basename(absoluteFilePath),
4932
5609
  filePath: absoluteFilePath
4933
5610
  })
4934
5611
  );
@@ -4948,7 +5625,7 @@ data: ${Date.now()}
4948
5625
  try {
4949
5626
  const parsed = JSON.parse(body);
4950
5627
  if (typeof parsed.content === "string") {
4951
- fs7.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
5628
+ fs8.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
4952
5629
  broadcastReload();
4953
5630
  res.writeHead(200, { "Content-Type": "application/json" });
4954
5631
  res.end(JSON.stringify({ success: true, savedAt: Date.now() }));
@@ -4967,11 +5644,11 @@ data: ${Date.now()}
4967
5644
  if (url.pathname === "/api/export" && (req.method === "GET" || req.method === "POST")) {
4968
5645
  const format = url.searchParams.get("format") || "docx";
4969
5646
  try {
4970
- const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5647
+ const mdContent = fs8.readFileSync(absoluteFilePath, "utf-8");
4971
5648
  const doc = parseMarkdownDocument(mdContent);
4972
5649
  const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
4973
5650
  const mergedConfig = { ...baseConfig, ...resolvedConfig };
4974
- const fileBase = path7.basename(absoluteFilePath, path7.extname(absoluteFilePath));
5651
+ const fileBase = path8.basename(absoluteFilePath, path8.extname(absoluteFilePath));
4975
5652
  if (format === "docx") {
4976
5653
  const buffer = await buildDocxDocument(doc, mergedConfig, baseDir);
4977
5654
  res.writeHead(200, {
@@ -5006,7 +5683,7 @@ data: ${Date.now()}
5006
5683
  }
5007
5684
  if (url.pathname === "/document-content") {
5008
5685
  try {
5009
- const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5686
+ const mdContent = fs8.readFileSync(absoluteFilePath, "utf-8");
5010
5687
  const doc = parseMarkdownDocument(mdContent);
5011
5688
  const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5012
5689
  const html = await buildHtmlDocument(doc, { ...baseConfig, ...resolvedConfig }, baseDir);
@@ -5039,8 +5716,8 @@ data: ${Date.now()}
5039
5716
  return;
5040
5717
  }
5041
5718
  if (url.pathname === "/" || url.pathname === "/index.html") {
5042
- const fileName = path7.basename(absoluteFilePath);
5043
- const initialContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5719
+ const fileName = path8.basename(absoluteFilePath);
5720
+ const initialContent = fs8.readFileSync(absoluteFilePath, "utf-8");
5044
5721
  const appHtml = `<!DOCTYPE html>
5045
5722
  <html lang="en">
5046
5723
  <head>
@@ -5744,9 +6421,9 @@ function defineConfig(config) {
5744
6421
  }
5745
6422
 
5746
6423
  // src/version.ts
5747
- import * as fs8 from "fs";
5748
- import * as path8 from "path";
5749
- import { fileURLToPath } from "url";
6424
+ import * as fs9 from "fs";
6425
+ import * as path9 from "path";
6426
+ import { fileURLToPath as fileURLToPath2 } from "url";
5750
6427
  try {
5751
6428
  if (typeof globalThis !== "undefined" && (!globalThis.localStorage || typeof globalThis.localStorage.getItem !== "function")) {
5752
6429
  Object.defineProperty(globalThis, "localStorage", {
@@ -5767,32 +6444,33 @@ try {
5767
6444
  }
5768
6445
  } catch {
5769
6446
  }
5770
- var FALLBACK_VERSION = "0.4.0";
5771
6447
  function readVersionFromPackageJson(fromDir) {
5772
6448
  let currentDir = fromDir;
5773
- for (let i = 0; i < 6; i++) {
6449
+ for (let i = 0; i < 10; i++) {
5774
6450
  try {
5775
- const pkgJsonPath = path8.join(currentDir, "package.json");
5776
- if (fs8.existsSync(pkgJsonPath)) {
5777
- const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
6451
+ const pkgJsonPath = path9.join(currentDir, "package.json");
6452
+ if (fs9.existsSync(pkgJsonPath)) {
6453
+ const pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
5778
6454
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
5779
6455
  return pkg.version;
5780
6456
  }
5781
6457
  }
5782
6458
  } catch {
5783
6459
  }
5784
- const parentDir = path8.dirname(currentDir);
6460
+ const parentDir = path9.dirname(currentDir);
5785
6461
  if (parentDir === currentDir) break;
5786
6462
  currentDir = parentDir;
5787
6463
  }
5788
- return FALLBACK_VERSION;
6464
+ throw new Error(
6465
+ "Failed to resolve '@masumdev/markforge' package version: package.json was not found or is missing a valid 'version' field."
6466
+ );
5789
6467
  }
5790
6468
  function getPackageDir() {
5791
6469
  if (typeof __dirname !== "undefined") {
5792
6470
  return __dirname;
5793
6471
  }
5794
6472
  try {
5795
- return path8.dirname(fileURLToPath(import.meta.url));
6473
+ return path9.dirname(fileURLToPath2(import.meta.url));
5796
6474
  } catch {
5797
6475
  return process.cwd();
5798
6476
  }
@@ -5802,6 +6480,8 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
5802
6480
  return readVersionFromPackageJson(fromDir);
5803
6481
  }
5804
6482
  export {
6483
+ BackCoverPreset,
6484
+ CoverPagePreset,
5805
6485
  DEFAULT_CONFIG,
5806
6486
  KATEX_INLINE_CSS,
5807
6487
  MARKFORGE_VERSION,
@@ -5810,15 +6490,21 @@ export {
5810
6490
  PAPER_DIMENSIONS_TWIP,
5811
6491
  PaperSizeEnum,
5812
6492
  SYNTAX_COLORS,
6493
+ SignatureAlign,
6494
+ SignatureStyle,
5813
6495
  SyntaxTheme,
5814
6496
  THEMES,
5815
6497
  THEME_CORPORATE,
5816
6498
  THEME_DEFAULT,
5817
6499
  Theme,
5818
6500
  WatermarkPosition,
6501
+ applyHeadingNumbering,
5819
6502
  buildDocxDocument,
5820
6503
  buildHtmlDocument,
5821
6504
  buildPdfDocument,
6505
+ buildPngDocument,
6506
+ buildTextDocument,
6507
+ buildTextDocument as buildTxtDocument,
5822
6508
  compileMarkdown,
5823
6509
  defineConfig,
5824
6510
  escapeHtml,
@@ -5847,6 +6533,7 @@ export {
5847
6533
  renderBackCoverHtml,
5848
6534
  renderCoverPageHtml,
5849
6535
  renderInlinesToHtml,
6536
+ renderInlinesToText,
5850
6537
  renderMathToHtml,
5851
6538
  renderMermaidToPng,
5852
6539
  renderNodesToHtml,