@masumdev/markforge 0.4.0 → 0.4.1

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.
@@ -183,7 +183,16 @@ function parseInlineSpans(text) {
183
183
  remaining = remaining.slice(nextSpecial);
184
184
  }
185
185
  }
186
- return spans;
186
+ const merged = [];
187
+ for (const s of spans) {
188
+ const prev = merged[merged.length - 1];
189
+ if (prev && prev.type === "text" && s.type === "text") {
190
+ prev.content += s.content;
191
+ } else {
192
+ merged.push(s);
193
+ }
194
+ }
195
+ return merged;
187
196
  }
188
197
  function slugify(text) {
189
198
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -195,6 +204,7 @@ function applyHeadingNumbering(nodes, tocEntries, options) {
195
204
  const counters = [0, 0, 0, 0, 0, 0];
196
205
  for (const node of nodes) {
197
206
  if (node.type === "heading" && node.level) {
207
+ if (node._numbered) continue;
198
208
  const lvl = node.level;
199
209
  if (lvl > depth) continue;
200
210
  if (lvl === 1 && skipH1) continue;
@@ -210,6 +220,7 @@ function applyHeadingNumbering(nodes, tocEntries, options) {
210
220
  const originalText = node.text || "";
211
221
  node.text = fullPrefix + originalText;
212
222
  node.inlines = parseInlineSpans(node.text);
223
+ node._numbered = true;
213
224
  const toc = tocEntries.find((t) => t.id === node.id);
214
225
  if (toc) {
215
226
  toc.text = node.text;
@@ -665,7 +676,7 @@ var THEME_COMPONENTS = `
665
676
  .document-meta { font-size: 0.9rem; color: var(--mf-text-muted); display: flex; gap: 1.5rem; flex-wrap: wrap; }
666
677
 
667
678
  /* Table of Contents */
668
- .table-of-contents { background: var(--mf-card-bg); border: 1px solid var(--mf-border); border-radius: 8px; padding: 1.5rem 2rem; margin: 2rem 0; }
679
+ .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; }
669
680
  .table-of-contents h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--mf-text-muted); margin: 0 0 1rem 0; }
670
681
  .table-of-contents ul { list-style: none; padding: 0; margin: 0; }
671
682
  .table-of-contents li { padding: 0.25rem 0; }
@@ -738,11 +749,13 @@ var THEME_CORPORATE = `
738
749
  }
739
750
  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; }
740
751
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
741
- 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; }
742
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
752
+ 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; }
753
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
743
754
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
744
- h3 { font-size: 1.3rem; }
745
- h4 { font-size: 1.1rem; }
755
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
756
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
757
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
758
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
746
759
  p { margin: 0.8rem 0; }
747
760
  `;
748
761
  var THEMES = {
@@ -783,11 +796,13 @@ function generateThemeCss(theme) {
783
796
  }
784
797
  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; }
785
798
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
786
- 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; }
787
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
799
+ 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; }
800
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
788
801
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid var(--mf-border); padding-bottom: 0.4rem; }
789
- h3 { font-size: 1.3rem; }
790
- h4 { font-size: 1.1rem; }
802
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
803
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
804
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
805
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
791
806
  p { margin: 0.8rem 0; }
792
807
  ${theme.customCss || ""}
793
808
  `;
@@ -1197,16 +1212,45 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1197
1212
  if (/^[0-9.]+$/.test(str)) return `${str}pt`;
1198
1213
  return str;
1199
1214
  }
1200
- function replaceDocumentTokens(template = "", meta) {
1201
- const currentYear = meta.year || (/* @__PURE__ */ new Date()).getFullYear().toString();
1202
- 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 || "");
1215
+ function replaceDocumentTokens(template = "", meta = {}) {
1216
+ if (!template) return "";
1217
+ const currentYear = meta.year ? String(meta.year) : (/* @__PURE__ */ new Date()).getFullYear().toString();
1218
+ const tokenMap = {
1219
+ title: meta.title ? String(meta.title) : "",
1220
+ subtitle: meta.subtitle ? String(meta.subtitle) : "",
1221
+ author: meta.author ? Array.isArray(meta.author) ? meta.author.join(", ") : String(meta.author) : "",
1222
+ version: meta.version ? String(meta.version) : "",
1223
+ date: meta.date ? String(meta.date) : "",
1224
+ company: meta.company ? String(meta.company) : "",
1225
+ year: currentYear
1226
+ };
1227
+ if (meta.metadata && typeof meta.metadata === "object") {
1228
+ for (const [key, val] of Object.entries(meta.metadata)) {
1229
+ if (val !== void 0 && val !== null) {
1230
+ tokenMap[key.toLowerCase()] = String(val);
1231
+ }
1232
+ }
1233
+ }
1234
+ for (const [key, val] of Object.entries(meta)) {
1235
+ if (val !== void 0 && val !== null && typeof val !== "object") {
1236
+ tokenMap[key.toLowerCase()] = String(val);
1237
+ }
1238
+ }
1239
+ return template.replace(/\{([a-zA-Z0-9_\-]+)\}/gi, (match, tokenKey) => {
1240
+ const lowerKey = tokenKey.toLowerCase();
1241
+ if (lowerKey in tokenMap) {
1242
+ return tokenMap[lowerKey];
1243
+ }
1244
+ return match;
1245
+ });
1203
1246
  }
1204
- function normalizeWatermark(rawWatermark) {
1247
+ function normalizeWatermark(rawWatermark, tokens) {
1205
1248
  if (!rawWatermark) {
1206
1249
  return void 0;
1207
1250
  }
1208
1251
  if (typeof rawWatermark === "string") {
1209
- const text = rawWatermark.trim();
1252
+ let text = rawWatermark.trim();
1253
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1210
1254
  if (!text) return void 0;
1211
1255
  return {
1212
1256
  text,
@@ -1219,8 +1263,10 @@ function normalizeWatermark(rawWatermark) {
1219
1263
  }
1220
1264
  if (typeof rawWatermark === "object") {
1221
1265
  if (!rawWatermark.text || !rawWatermark.text.trim()) return void 0;
1266
+ let text = rawWatermark.text.trim();
1267
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1222
1268
  return {
1223
- text: rawWatermark.text.trim(),
1269
+ text,
1224
1270
  color: rawWatermark.color || "#94a3b8",
1225
1271
  opacity: typeof rawWatermark.opacity === "number" ? rawWatermark.opacity : 0.08,
1226
1272
  fontSize: rawWatermark.fontSize || 54,
@@ -1341,27 +1387,46 @@ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1341
1387
  const cfg = typeof rawCover === "object" ? rawCover : {};
1342
1388
  if (cfg.enabled === false) return void 0;
1343
1389
  const preset = cfg.preset || "modern";
1344
- const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title || "Document Title";
1345
- const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle;
1346
- const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author;
1347
- const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1348
- const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version;
1390
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title ? String(tokenCtx.title) : "Document Title";
1391
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle ? String(tokenCtx.subtitle) : void 0;
1392
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1393
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1394
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1349
1395
  let dateStr;
1350
1396
  if (typeof cfg.date === "string") {
1351
1397
  dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
1352
1398
  } else if (cfg.date === true) {
1353
- dateStr = tokenCtx.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1399
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1354
1400
  } else {
1355
- dateStr = tokenCtx.date;
1401
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : void 0;
1356
1402
  }
1357
1403
  const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1358
1404
  const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1359
1405
  const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1360
1406
  const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1361
1407
  const logoWidth = cfg.logoWidth;
1362
- const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1408
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1409
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1363
1410
  const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1411
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1412
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1413
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1364
1414
  const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1415
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1416
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1417
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1418
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1419
+ let socialMap;
1420
+ if (cfg.social && typeof cfg.social === "object") {
1421
+ socialMap = {};
1422
+ for (const [k, v] of Object.entries(cfg.social)) {
1423
+ if (typeof v === "string") {
1424
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1425
+ }
1426
+ }
1427
+ }
1428
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1429
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : void 0;
1365
1430
  return {
1366
1431
  enabled: true,
1367
1432
  preset,
@@ -1377,8 +1442,18 @@ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1377
1442
  logo,
1378
1443
  logoWidth,
1379
1444
  bgGradient,
1445
+ backgroundColor,
1380
1446
  textColor,
1381
- footerText
1447
+ titleColor,
1448
+ subtitleColor,
1449
+ accentColor,
1450
+ footerText,
1451
+ address,
1452
+ email,
1453
+ phone,
1454
+ website,
1455
+ social: socialMap,
1456
+ copyright
1382
1457
  };
1383
1458
  }
1384
1459
  function normalizeBackCover(rawBack, tokenCtx = {}) {
@@ -1388,7 +1463,10 @@ function normalizeBackCover(rawBack, tokenCtx = {}) {
1388
1463
  const preset = cfg.preset || "modern";
1389
1464
  const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
1390
1465
  const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
1391
- const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
1466
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1467
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1468
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1469
+ const date = typeof cfg.date === "string" ? replaceDocumentTokens(cfg.date, tokenCtx) : tokenCtx.date ? String(tokenCtx.date) : void 0;
1392
1470
  const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1393
1471
  const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1394
1472
  const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
@@ -1404,32 +1482,45 @@ function normalizeBackCover(rawBack, tokenCtx = {}) {
1404
1482
  }
1405
1483
  const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1406
1484
  const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
1485
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1407
1486
  const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1408
1487
  const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1409
1488
  const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1410
1489
  const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1411
1490
  const logoWidth = cfg.logoWidth;
1412
- const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
1491
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1492
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1413
1493
  const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1494
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1495
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1496
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1414
1497
  return {
1415
1498
  enabled: true,
1416
1499
  preset,
1417
1500
  title,
1418
1501
  subtitle,
1502
+ author,
1419
1503
  company,
1504
+ version,
1505
+ date,
1420
1506
  address,
1421
1507
  email,
1422
1508
  phone,
1423
1509
  website,
1424
1510
  social: socialMap,
1425
1511
  copyright,
1512
+ footerText,
1426
1513
  badge,
1427
1514
  badgeColor,
1428
1515
  badgeTextColor,
1429
1516
  logo,
1430
1517
  logoWidth,
1431
1518
  bgGradient,
1432
- textColor
1519
+ backgroundColor,
1520
+ textColor,
1521
+ titleColor,
1522
+ subtitleColor,
1523
+ accentColor
1433
1524
  };
1434
1525
  }
1435
1526
  function normalizeNumberHeadings(raw) {
@@ -1469,7 +1560,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1469
1560
  const version = mergedMeta.version || void 0;
1470
1561
  const company = mergedMeta.company || void 0;
1471
1562
  const lang = mergedMeta.lang || "en";
1472
- const tokenContext = { title, subtitle, author, version, date, company };
1563
+ const tokenContext = {
1564
+ ...mergedMeta,
1565
+ title,
1566
+ subtitle,
1567
+ author,
1568
+ version,
1569
+ date,
1570
+ company
1571
+ };
1473
1572
  const theme = mergedMeta.theme || userConfig.theme || DEFAULT_CONFIG.theme;
1474
1573
  const orientation = mergedMeta.orientation || userConfig.orientation || DEFAULT_CONFIG.orientation;
1475
1574
  const paperSize = mergedMeta.paperSize || userConfig.paperSize || DEFAULT_CONFIG.paperSize;
@@ -1498,7 +1597,7 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1498
1597
  const footer = normalizeHeaderFooter(rawFooter, tokenContext);
1499
1598
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1500
1599
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1501
- const watermark = normalizeWatermark(rawWatermark);
1600
+ const watermark = normalizeWatermark(rawWatermark, tokenContext);
1502
1601
  const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1503
1602
  const signatures = normalizeSignatures(rawSignatures, tokenContext);
1504
1603
  const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
@@ -1594,7 +1693,7 @@ var KATEX_INLINE_CSS = `
1594
1693
  function escapeHtml(str) {
1595
1694
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1596
1695
  }
1597
- async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1696
+ async function renderInlinesToHtml(spans = [], baseDir = process.cwd(), tokens) {
1598
1697
  let result = "";
1599
1698
  for (const span of spans) {
1600
1699
  if (span.type === "image" && span.url) {
@@ -1608,23 +1707,23 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1608
1707
  continue;
1609
1708
  }
1610
1709
  if (span.type === "link" && span.url) {
1611
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1710
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1612
1711
  const title = span.title ? ` title="${escapeHtml(span.title)}"` : "";
1613
1712
  result += `<a href="${escapeHtml(span.url)}"${title}>${inner}</a>`;
1614
1713
  continue;
1615
1714
  }
1616
1715
  if (span.type === "bold") {
1617
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1716
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1618
1717
  result += `<strong>${inner}</strong>`;
1619
1718
  continue;
1620
1719
  }
1621
1720
  if (span.type === "italic") {
1622
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1721
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1623
1722
  result += `<em>${inner}</em>`;
1624
1723
  continue;
1625
1724
  }
1626
1725
  if (span.type === "strikethrough") {
1627
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1726
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1628
1727
  result += `<del>${inner}</del>`;
1629
1728
  continue;
1630
1729
  }
@@ -1645,21 +1744,23 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1645
1744
  result += span.content;
1646
1745
  continue;
1647
1746
  }
1648
- result += escapeHtml(span.content);
1747
+ const content = tokens ? replaceDocumentTokens(span.content, tokens) : span.content;
1748
+ result += escapeHtml(content);
1649
1749
  }
1650
1750
  return result;
1651
1751
  }
1652
- async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1752
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd(), tokens) {
1653
1753
  let bodyHtml = "";
1754
+ const tokenCtx = tokens || resolved;
1654
1755
  for (const node of nodes) {
1655
1756
  if (node.type === "heading") {
1656
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
1757
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1657
1758
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
1658
1759
  `;
1659
1760
  continue;
1660
1761
  }
1661
1762
  if (node.type === "paragraph") {
1662
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
1763
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1663
1764
  bodyHtml += ` <p>${inner}</p>
1664
1765
  `;
1665
1766
  continue;
@@ -1674,7 +1775,7 @@ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
1674
1775
  const gap = node.columnGap || "1.5rem";
1675
1776
  let colChildrenHtml = "";
1676
1777
  for (const col of node.children || []) {
1677
- const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir);
1778
+ const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir, tokenCtx);
1678
1779
  colChildrenHtml += ` <div class="markforge-col">
1679
1780
  ${colInner} </div>
1680
1781
  `;
@@ -1700,7 +1801,7 @@ ${escapeHtml(node.text || "")}
1700
1801
  continue;
1701
1802
  }
1702
1803
  if (node.type === "callout") {
1703
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
1804
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1704
1805
  const CALLOUT_STYLES = {
1705
1806
  NOTE: { bg: "#ECFDFD", border: "#33CDCF", titleColor: "#009DA0" },
1706
1807
  TIP: { bg: "#ecfdf5", border: "#10b981", titleColor: "#10b981" },
@@ -1720,7 +1821,7 @@ ${escapeHtml(node.text || "")}
1720
1821
  continue;
1721
1822
  }
1722
1823
  if (node.type === "blockquote") {
1723
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
1824
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1724
1825
  bodyHtml += ` <blockquote>${inner}</blockquote>
1725
1826
  `;
1726
1827
  continue;
@@ -1734,7 +1835,7 @@ ${escapeHtml(node.text || "")}
1734
1835
  for (const cell of row.children || []) {
1735
1836
  const tag = cell.isHeader ? "th" : "td";
1736
1837
  const align = cell.align ? ` align="${cell.align}"` : "";
1737
- const inner = await renderInlinesToHtml(cell.inlines, baseDir);
1838
+ const inner = await renderInlinesToHtml(cell.inlines, baseDir, tokenCtx);
1738
1839
  bodyHtml += ` <${tag}${align}>${inner}</${tag}>
1739
1840
  `;
1740
1841
  }
@@ -1750,7 +1851,7 @@ ${escapeHtml(node.text || "")}
1750
1851
  bodyHtml += ` <${tag}>
1751
1852
  `;
1752
1853
  for (const item of node.children) {
1753
- const inner = await renderInlinesToHtml(item.inlines, baseDir);
1854
+ const inner = await renderInlinesToHtml(item.inlines, baseDir, tokenCtx);
1754
1855
  bodyHtml += ` <li>${inner}</li>
1755
1856
  `;
1756
1857
  }
@@ -2064,7 +2165,7 @@ async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
2064
2165
  return { html, css };
2065
2166
  }
2066
2167
  async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2067
- var _a;
2168
+ var _a, _b;
2068
2169
  const resolved = resolveDocumentConfig(doc.metadata, config);
2069
2170
  const baseThemeCss = generateThemeCss(resolved.theme);
2070
2171
  let customCss = "";
@@ -2160,6 +2261,9 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2160
2261
  bodyHtml += ` </header>
2161
2262
  `;
2162
2263
  }
2264
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
2265
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
2266
+ }
2163
2267
  if (resolved.toc && doc.tocEntries.length > 0) {
2164
2268
  bodyHtml += ` <nav class="table-of-contents">
2165
2269
  `;
@@ -2175,12 +2279,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2175
2279
  </nav>
2176
2280
  `;
2177
2281
  }
2178
- bodyHtml += await renderNodesToHtml(doc.nodes, resolved, baseDir);
2282
+ const mergedTokens = {
2283
+ ...config.metadata,
2284
+ ...doc.metadata,
2285
+ ...resolved,
2286
+ title: resolved.title,
2287
+ subtitle: resolved.subtitle,
2288
+ author: resolved.author,
2289
+ version: resolved.version,
2290
+ date: resolved.date,
2291
+ company: resolved.company
2292
+ };
2293
+ const nodesHtml = await renderNodesToHtml(doc.nodes, resolved, baseDir, mergedTokens);
2294
+ bodyHtml += ` <main class="markforge-content-body">
2295
+ ${nodesHtml} </main>
2296
+ `;
2179
2297
  let footnotesHtml = "";
2180
2298
  if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
2181
2299
  let fnListHtml = "";
2182
2300
  for (const def of doc.footnoteDefs) {
2183
- const defInner = await renderInlinesToHtml(def.inlines, baseDir);
2301
+ const defInner = await renderInlinesToHtml(def.inlines, baseDir, mergedTokens);
2184
2302
  fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">&#8617;</a></li>
2185
2303
  `;
2186
2304
  }
@@ -2218,17 +2336,7 @@ ${fnListHtml} </ol>
2218
2336
  }
2219
2337
  @media print {
2220
2338
  .document-watermark {
2221
- position: fixed;
2222
- top: 0;
2223
- left: 0;
2224
- right: 0;
2225
- bottom: 0;
2226
- width: 100vw;
2227
- height: 100vh;
2228
- pointer-events: none;
2229
- z-index: 0;
2230
- -webkit-print-color-adjust: exact;
2231
- print-color-adjust: exact;
2339
+ display: none !important;
2232
2340
  }
2233
2341
  }
2234
2342
  `;
@@ -2555,7 +2663,7 @@ function findChromeExecutable() {
2555
2663
  return null;
2556
2664
  }
2557
2665
  function injectPagedMediaStyles(html, config, metadata) {
2558
- var _a, _b, _c, _d, _e, _f, _g, _h;
2666
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2559
2667
  const resolved = resolveDocumentConfig(metadata || {}, config);
2560
2668
  const size = resolved.paperSize;
2561
2669
  const orientation = resolved.orientation;
@@ -2630,9 +2738,11 @@ function injectPagedMediaStyles(html, config, metadata) {
2630
2738
  min-height: 100vh;
2631
2739
  height: 100vh;
2632
2740
  box-sizing: border-box;
2741
+ break-before: page;
2742
+ break-after: avoid;
2633
2743
  }` : "";
2634
- const pagedCss = `
2635
- @page {
2744
+ const tocPageCss = resolved.toc ? `
2745
+ @page toc-page {
2636
2746
  size: ${size} ${orientation};
2637
2747
  margin-top: ${top};
2638
2748
  margin-bottom: ${bottom};
@@ -2643,9 +2753,36 @@ function injectPagedMediaStyles(html, config, metadata) {
2643
2753
  ${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
2644
2754
  ${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
2645
2755
  ${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
2646
- ${buildZoneCss("bottom-right", (_h = resolved.footer) == null ? void 0 : _h.right, true)}
2756
+ @bottom-right {
2757
+ content: counter(page, lower-roman);
2758
+ font-size: 9pt;
2759
+ color: #94a3b8;
2760
+ }
2761
+ }
2762
+ .table-of-contents {
2763
+ page: toc-page;
2764
+ page-break-after: always;
2765
+ break-after: page;
2766
+ }
2767
+ .markforge-content-body {
2768
+ counter-reset: page 1;
2769
+ }` : "";
2770
+ const pagedCss = `
2771
+ @page {
2772
+ size: ${size} ${orientation};
2773
+ margin-top: ${top};
2774
+ margin-bottom: ${bottom};
2775
+ margin-left: ${left};
2776
+ margin-right: ${right};
2777
+ ${buildZoneCss("top-left", (_h = resolved.header) == null ? void 0 : _h.left)}
2778
+ ${buildZoneCss("top-center", (_i = resolved.header) == null ? void 0 : _i.center)}
2779
+ ${buildZoneCss("top-right", (_j = resolved.header) == null ? void 0 : _j.right)}
2780
+ ${buildZoneCss("bottom-left", (_k = resolved.footer) == null ? void 0 : _k.left)}
2781
+ ${buildZoneCss("bottom-center", (_l = resolved.footer) == null ? void 0 : _l.center)}
2782
+ ${buildZoneCss("bottom-right", (_m = resolved.footer) == null ? void 0 : _m.right, true)}
2647
2783
  }
2648
2784
  ${coverPageCss}
2785
+ ${tocPageCss}
2649
2786
  ${backCoverCss}
2650
2787
  @media print {
2651
2788
  body { padding: 0; }
@@ -2776,9 +2913,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2776
2913
  const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
2777
2914
  if (wmPng) {
2778
2915
  const embeddedPng = await pdfDoc.embedPng(wmPng);
2916
+ const totalPages = pdfDoc.getPageCount();
2779
2917
  const pages = pdfDoc.getPages();
2780
2918
  const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
2781
- const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? pages.length - 1 : pages.length;
2919
+ const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? totalPages - 1 : totalPages;
2782
2920
  for (let i = startPageIndex; i < endPageIndex; i++) {
2783
2921
  const page = pages[i];
2784
2922
  const { width, height } = page.getSize();
@@ -2863,7 +3001,9 @@ import {
2863
3001
  convertMillimetersToTwip,
2864
3002
  ShadingType,
2865
3003
  ExternalHyperlink,
2866
- TabStopType
3004
+ TabStopType,
3005
+ PageBreak,
3006
+ NumberFormat
2867
3007
  } from "docx";
2868
3008
 
2869
3009
  // src/core/mermaid/mermaidRenderer.ts
@@ -3125,7 +3265,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
3125
3265
  return runs;
3126
3266
  }
3127
3267
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3128
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
3268
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
3129
3269
  const resolved = resolveDocumentConfig(doc.metadata, config);
3130
3270
  const docElements = [];
3131
3271
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -3197,6 +3337,9 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3197
3337
  );
3198
3338
  }
3199
3339
  }
3340
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
3341
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
3342
+ }
3200
3343
  if (resolved.toc) {
3201
3344
  const headingNodes = doc.nodes.filter(
3202
3345
  (n) => n.type === "heading" && typeof n.level === "number" && n.level >= 1 && n.level <= 3
@@ -3266,7 +3409,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3266
3409
  ]
3267
3410
  });
3268
3411
  docElements.push(tocCard);
3269
- docElements.push(new Paragraph({ spacing: { after: 200 } }));
3412
+ docElements.push(
3413
+ new Paragraph({
3414
+ children: [new PageBreak()]
3415
+ })
3416
+ );
3270
3417
  }
3271
3418
  }
3272
3419
  for (const node of doc.nodes) {
@@ -3276,7 +3423,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3276
3423
  font: defaultFont,
3277
3424
  size: 34,
3278
3425
  // 17pt
3279
- color: textHex,
3426
+ color: primaryDarkHex,
3280
3427
  bold: true
3281
3428
  });
3282
3429
  docElements.push(
@@ -3322,7 +3469,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3322
3469
  font: defaultFont,
3323
3470
  size: 22,
3324
3471
  // 11pt
3325
- color: textHex,
3472
+ color: primaryDarkHex,
3326
3473
  bold: true
3327
3474
  });
3328
3475
  docElements.push(
@@ -3552,7 +3699,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3552
3699
  }
3553
3700
  if (node.type === "table" && node.children) {
3554
3701
  const tableRows = [];
3555
- const numCols = ((_c = (_b = node.children[0]) == null ? void 0 : _b.children) == null ? void 0 : _c.length) || 1;
3702
+ const numCols = ((_d = (_c = node.children[0]) == null ? void 0 : _c.children) == null ? void 0 : _d.length) || 1;
3556
3703
  const colWidth = Math.floor(9e3 / numCols);
3557
3704
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
3558
3705
  const rowNode = node.children[rowIdx];
@@ -3562,7 +3709,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3562
3709
  if (rowNode.children) {
3563
3710
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
3564
3711
  const cellNode = rowNode.children[colIdx];
3565
- const align = (_d = node.align) == null ? void 0 : _d[colIdx];
3712
+ const align = (_e = node.align) == null ? void 0 : _e[colIdx];
3566
3713
  let alignment = AlignmentType.LEFT;
3567
3714
  if (align === "center") alignment = AlignmentType.CENTER;
3568
3715
  if (align === "right") alignment = AlignmentType.RIGHT;
@@ -3869,7 +4016,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3869
4016
  const centerPos = Math.round(contentWidthTwip / 2);
3870
4017
  const rightPos = contentWidthTwip;
3871
4018
  const headerRuns = [];
3872
- if ((_e = resolved.header) == null ? void 0 : _e.left) {
4019
+ if ((_f = resolved.header) == null ? void 0 : _f.left) {
3873
4020
  headerRuns.push(
3874
4021
  new TextRun({
3875
4022
  text: resolved.header.left.text,
@@ -3882,7 +4029,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3882
4029
  );
3883
4030
  }
3884
4031
  headerRuns.push(new TextRun({ text: " " }));
3885
- if ((_f = resolved.header) == null ? void 0 : _f.center) {
4032
+ if ((_g = resolved.header) == null ? void 0 : _g.center) {
3886
4033
  headerRuns.push(
3887
4034
  new TextRun({
3888
4035
  text: resolved.header.center.text,
@@ -3895,7 +4042,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3895
4042
  );
3896
4043
  }
3897
4044
  headerRuns.push(new TextRun({ text: " " }));
3898
- if ((_g = resolved.header) == null ? void 0 : _g.right) {
4045
+ if ((_h = resolved.header) == null ? void 0 : _h.right) {
3899
4046
  headerRuns.push(
3900
4047
  new TextRun({
3901
4048
  text: resolved.header.right.text,
@@ -3934,7 +4081,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3934
4081
  ]
3935
4082
  }) : void 0;
3936
4083
  const footerRuns = [];
3937
- if ((_h = resolved.footer) == null ? void 0 : _h.left) {
4084
+ if ((_i = resolved.footer) == null ? void 0 : _i.left) {
3938
4085
  footerRuns.push(
3939
4086
  new TextRun({
3940
4087
  text: resolved.footer.left.text,
@@ -3947,7 +4094,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3947
4094
  );
3948
4095
  }
3949
4096
  footerRuns.push(new TextRun({ text: " " }));
3950
- if ((_i = resolved.footer) == null ? void 0 : _i.center) {
4097
+ if ((_j = resolved.footer) == null ? void 0 : _j.center) {
3951
4098
  footerRuns.push(
3952
4099
  new TextRun({
3953
4100
  text: resolved.footer.center.text,
@@ -3960,7 +4107,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3960
4107
  );
3961
4108
  }
3962
4109
  footerRuns.push(new TextRun({ text: " " }));
3963
- if ((_j = resolved.footer) == null ? void 0 : _j.right) {
4110
+ if ((_k = resolved.footer) == null ? void 0 : _k.right) {
3964
4111
  const rZone = resolved.footer.right;
3965
4112
  const rColor = rZone.color.replace("#", "");
3966
4113
  const rSize = (rZone.fontSize || 9) * 2;
@@ -4080,6 +4227,10 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
4080
4227
  docSections.push({
4081
4228
  properties: {
4082
4229
  page: {
4230
+ pageNumbers: {
4231
+ start: 1,
4232
+ formatType: NumberFormat.DECIMAL
4233
+ },
4083
4234
  size: {
4084
4235
  width: resolved.paperDimensions.widthTwip,
4085
4236
  height: resolved.paperDimensions.heightTwip,