@masumdev/markforge 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -69,6 +69,7 @@ __export(src_exports, {
69
69
  markforge: () => compileMarkdown,
70
70
  normalizeHeaderFooter: () => normalizeHeaderFooter,
71
71
  normalizeHeaderFooterSlot: () => normalizeHeaderFooterSlot,
72
+ normalizeSignatures: () => normalizeSignatures,
72
73
  normalizeWatermark: () => normalizeWatermark,
73
74
  parseInlineSpans: () => parseInlineSpans,
74
75
  parseMarginToTwip: () => parseMarginToTwip2,
@@ -1395,6 +1396,67 @@ function normalizeHeaderFooter(raw, meta) {
1395
1396
  dividerColor: raw.dividerColor || "#E2E8F0"
1396
1397
  };
1397
1398
  }
1399
+ function normalizeSignatures(raw, meta = {}) {
1400
+ if (!raw) return void 0;
1401
+ let rawItems = [];
1402
+ let rawConfig = {};
1403
+ if (Array.isArray(raw)) {
1404
+ rawItems = raw;
1405
+ } else if (typeof raw === "object" && Array.isArray(raw.items)) {
1406
+ rawItems = raw.items;
1407
+ rawConfig = raw;
1408
+ }
1409
+ if (rawItems.length === 0) return void 0;
1410
+ const cappedItems = rawItems.slice(0, 4);
1411
+ const items = cappedItems.map((item) => {
1412
+ const tokenCtx = meta;
1413
+ const rawName = typeof item.name === "string" ? item.name : "";
1414
+ const name = replaceDocumentTokens(rawName, tokenCtx).trim();
1415
+ const title = item.title ? replaceDocumentTokens(item.title, tokenCtx).trim() : void 0;
1416
+ const role = item.role ? replaceDocumentTokens(item.role, tokenCtx).trim() : void 0;
1417
+ let dateStr;
1418
+ if (typeof item.date === "string") {
1419
+ dateStr = replaceDocumentTokens(item.date, tokenCtx).trim();
1420
+ } else if (item.date === true) {
1421
+ dateStr = meta.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1422
+ }
1423
+ let signatureHeight = 60;
1424
+ if (typeof item.signatureHeight === "number") {
1425
+ signatureHeight = item.signatureHeight;
1426
+ } else if (typeof item.signatureHeight === "string") {
1427
+ const parsed = parseFloat(item.signatureHeight);
1428
+ if (!isNaN(parsed)) signatureHeight = parsed;
1429
+ }
1430
+ return {
1431
+ title,
1432
+ name: name || "Authorized Signatory",
1433
+ role,
1434
+ date: dateStr,
1435
+ image: item.image,
1436
+ signatureHeight
1437
+ };
1438
+ });
1439
+ const align = rawConfig.align || (items.length === 1 ? "right" : "space-between");
1440
+ const style = rawConfig.style || "line";
1441
+ const borderColor = rawConfig.borderColor || "#CBD5E1";
1442
+ const titleColor = rawConfig.titleColor || "#64748B";
1443
+ const nameColor = rawConfig.nameColor || "#0F172A";
1444
+ const roleColor = rawConfig.roleColor || "#64748B";
1445
+ const spacingBeforeRaw = rawConfig.spacingBefore ?? "2.5rem";
1446
+ const spacingBefore = formatMarginCss(spacingBeforeRaw, "2.5rem");
1447
+ const spacingBeforeTwip = parseMarginToTwip(spacingBeforeRaw, 600);
1448
+ return {
1449
+ items,
1450
+ align,
1451
+ style,
1452
+ borderColor,
1453
+ titleColor,
1454
+ nameColor,
1455
+ roleColor,
1456
+ spacingBefore,
1457
+ spacingBeforeTwip
1458
+ };
1459
+ }
1398
1460
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1399
1461
  const configMeta = userConfig.metadata || {};
1400
1462
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1435,6 +1497,8 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1435
1497
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1436
1498
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1437
1499
  const watermark = normalizeWatermark(rawWatermark);
1500
+ const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1501
+ const signatures = normalizeSignatures(rawSignatures, tokenContext);
1438
1502
  const cssList = [];
1439
1503
  const addCss = (item) => {
1440
1504
  if (!item) return;
@@ -1462,6 +1526,7 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1462
1526
  header,
1463
1527
  footer,
1464
1528
  toc,
1529
+ signatures,
1465
1530
  watermark,
1466
1531
  css: cssList,
1467
1532
  embedImages,
@@ -1684,26 +1749,160 @@ ${escapeHtml(node.text || "")}
1684
1749
  watermarkCss = `
1685
1750
  .document-watermark {
1686
1751
  position: fixed;
1687
- top: 50%;
1688
- left: 50%;
1689
- transform: translate(-50%, -50%) rotate(${wm.rotate}deg);
1690
- font-size: ${wm.fontSize}pt;
1691
- font-weight: 900;
1692
- color: ${wm.color};
1693
- opacity: ${wm.opacity};
1752
+ top: 0;
1753
+ left: 0;
1754
+ width: 100vw;
1755
+ height: 100vh;
1694
1756
  pointer-events: none;
1695
1757
  z-index: 0;
1696
1758
  user-select: none;
1697
- text-transform: uppercase;
1698
- letter-spacing: 0.15em;
1699
- white-space: nowrap;
1759
+ -webkit-user-select: none;
1700
1760
  }
1701
1761
  .document-container {
1702
1762
  position: relative;
1703
1763
  z-index: 1;
1704
1764
  }
1765
+ @media print {
1766
+ .document-watermark {
1767
+ position: fixed;
1768
+ top: 0;
1769
+ left: 0;
1770
+ width: 100vw;
1771
+ height: 100vh;
1772
+ -webkit-print-color-adjust: exact;
1773
+ print-color-adjust: exact;
1774
+ }
1775
+ }
1776
+ `;
1777
+ watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
1778
+ <script>
1779
+ (function() {
1780
+ try {
1781
+ var canvas = document.createElement('canvas');
1782
+ var dpr = 3;
1783
+ var width = 800;
1784
+ var height = 1100;
1785
+ canvas.width = Math.round(width * dpr);
1786
+ canvas.height = Math.round(height * dpr);
1787
+ var ctx = canvas.getContext('2d');
1788
+ if (ctx) {
1789
+ ctx.scale(dpr, dpr);
1790
+ ctx.translate(width / 2, height / 2);
1791
+ ctx.rotate((${wm.rotate} * Math.PI) / 180);
1792
+ ctx.textAlign = 'center';
1793
+ ctx.textBaseline = 'middle';
1794
+ ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
1795
+ ctx.fillStyle = '${wm.color}';
1796
+ ctx.globalAlpha = ${wm.opacity};
1797
+ try { ctx.letterSpacing = '0.15em'; } catch(e) {}
1798
+ ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
1799
+ var dataUrl = canvas.toDataURL('image/png');
1800
+ var wmEl = document.getElementById('markforge-watermark');
1801
+ if (wmEl) {
1802
+ wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
1803
+ wmEl.style.backgroundRepeat = 'no-repeat';
1804
+ wmEl.style.backgroundPosition = 'center center';
1805
+ wmEl.style.backgroundSize = 'contain';
1806
+ }
1807
+ }
1808
+ } catch(err) {}
1809
+ })();
1810
+ </script>
1811
+ `;
1812
+ }
1813
+ let signaturesHtml = "";
1814
+ let signaturesCss = "";
1815
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
1816
+ const sig = resolved.signatures;
1817
+ const numItems = sig.items.length;
1818
+ let justifyCss = "flex-end";
1819
+ if (sig.align === "left") justifyCss = "flex-start";
1820
+ else if (sig.align === "center") justifyCss = "center";
1821
+ else if (sig.align === "space-between") justifyCss = "space-between";
1822
+ signaturesCss = `
1823
+ .markforge-signatures {
1824
+ margin-top: ${sig.spacingBefore};
1825
+ display: grid;
1826
+ grid-template-columns: ${numItems === 1 ? sig.align === "left" ? "minmax(200px, 280px) 1fr" : sig.align === "center" ? "1fr minmax(200px, 280px) 1fr" : "1fr minmax(200px, 280px)" : `repeat(${numItems}, minmax(0, 1fr))`};
1827
+ gap: 2rem;
1828
+ page-break-inside: avoid;
1829
+ break-inside: avoid;
1830
+ }
1831
+ .markforge-signature-card {
1832
+ ${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
1833
+ ${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
1834
+ display: flex;
1835
+ flex-direction: column;
1836
+ ${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
1837
+ }
1838
+ .markforge-sig-title {
1839
+ font-size: 0.85rem;
1840
+ color: ${sig.titleColor};
1841
+ font-weight: 600;
1842
+ margin-bottom: 6px;
1843
+ }
1844
+ .markforge-sig-space {
1845
+ height: var(--sig-height, 60px);
1846
+ display: flex;
1847
+ align-items: center;
1848
+ justify-content: center;
1849
+ margin-bottom: 6px;
1850
+ }
1851
+ .markforge-sig-space img {
1852
+ max-height: 100%;
1853
+ max-width: 100%;
1854
+ object-fit: contain;
1855
+ }
1856
+ .markforge-sig-line {
1857
+ ${sig.style === "line" ? `border-bottom: 1.5px solid ${sig.borderColor}; margin-bottom: 8px;` : ""}
1858
+ }
1859
+ .markforge-sig-name {
1860
+ font-size: 0.95rem;
1861
+ font-weight: 700;
1862
+ color: ${sig.nameColor};
1863
+ }
1864
+ .markforge-sig-role {
1865
+ font-size: 0.82rem;
1866
+ color: ${sig.roleColor};
1867
+ margin-top: 2px;
1868
+ }
1869
+ .markforge-sig-date {
1870
+ font-size: 0.78rem;
1871
+ color: ${sig.roleColor};
1872
+ margin-top: 2px;
1873
+ }
1874
+ @media print {
1875
+ .markforge-signatures {
1876
+ page-break-inside: avoid;
1877
+ break-inside: avoid;
1878
+ }
1879
+ }
1705
1880
  `;
1706
- watermarkHtml = ` <div class="document-watermark">${escapeHtml(wm.text)}</div>
1881
+ const itemCards = sig.items.map((item) => {
1882
+ const titleHtml = item.title ? `<div class="markforge-sig-title">${escapeHtml(item.title)}</div>` : "";
1883
+ let signSpaceHtml = "";
1884
+ if (item.image) {
1885
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"><img src="${escapeHtml(item.image)}" alt="Signature" /></div>`;
1886
+ } else {
1887
+ signSpaceHtml = `<div class="markforge-sig-space" style="--sig-height: ${item.signatureHeight}px;"></div>`;
1888
+ }
1889
+ const lineHtml = sig.style === "line" ? `<div class="markforge-sig-line"></div>` : "";
1890
+ const nameHtml = `<div class="markforge-sig-name">${escapeHtml(item.name)}</div>`;
1891
+ const roleHtml = item.role ? `<div class="markforge-sig-role">${escapeHtml(item.role)}</div>` : "";
1892
+ const dateHtml = item.date ? `<div class="markforge-sig-date">Date: ${escapeHtml(item.date)}</div>` : "";
1893
+ return ` <div class="markforge-signature-card">
1894
+ ${titleHtml}
1895
+ ${signSpaceHtml}
1896
+ ${lineHtml}
1897
+ ${nameHtml}
1898
+ ${roleHtml}
1899
+ ${dateHtml}
1900
+ </div>`;
1901
+ }).join("\n");
1902
+ signaturesHtml = `
1903
+ <div class="markforge-signatures">
1904
+ ${itemCards}
1905
+ </div>
1707
1906
  `;
1708
1907
  }
1709
1908
  const hasMermaid = doc.nodes.some((n) => n.type === "mermaid");
@@ -1734,11 +1933,12 @@ ${baseThemeCss}
1734
1933
  ${customCss}
1735
1934
  ${inlinedCss}
1736
1935
  ${watermarkCss}
1936
+ ${signaturesCss}
1737
1937
  </style>
1738
1938
  </head>
1739
1939
  <body>
1740
1940
  ${watermarkHtml} <div class="document-container">
1741
- ${bodyHtml} </div>
1941
+ ${bodyHtml}${signaturesHtml} </div>
1742
1942
  ${mermaidScript}
1743
1943
  </body>
1744
1944
  </html>`;
@@ -2826,6 +3026,59 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2826
3026
  continue;
2827
3027
  }
2828
3028
  }
3029
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
3030
+ const sig = resolved.signatures;
3031
+ const numItems = sig.items.length;
3032
+ const contentWidth = Math.max(
3033
+ 1e3,
3034
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
3035
+ );
3036
+ docElements.push(new import_docx.Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
3037
+ const sigCells = [];
3038
+ const colWidths = [];
3039
+ if (numItems === 1) {
3040
+ const cardWidth = Math.min(3400, Math.floor(contentWidth * 0.42));
3041
+ const spacerWidth = contentWidth - cardWidth;
3042
+ const cardCell = await buildDocxSignatureCell(sig.items[0], sig, cardWidth, defaultFont, baseDir);
3043
+ if (sig.align === "left") {
3044
+ colWidths.push(cardWidth, spacerWidth);
3045
+ sigCells.push(cardCell, createEmptyDocxCell(spacerWidth));
3046
+ } else if (sig.align === "center") {
3047
+ const sideWidth = Math.floor(spacerWidth / 2);
3048
+ colWidths.push(sideWidth, cardWidth, sideWidth);
3049
+ sigCells.push(createEmptyDocxCell(sideWidth), cardCell, createEmptyDocxCell(sideWidth));
3050
+ } else {
3051
+ colWidths.push(spacerWidth, cardWidth);
3052
+ sigCells.push(createEmptyDocxCell(spacerWidth), cardCell);
3053
+ }
3054
+ } else {
3055
+ const colWidth = Math.floor(contentWidth / numItems);
3056
+ for (let i = 0; i < numItems; i++) {
3057
+ colWidths.push(colWidth);
3058
+ const cell = await buildDocxSignatureCell(sig.items[i], sig, colWidth, defaultFont, baseDir);
3059
+ sigCells.push(cell);
3060
+ }
3061
+ }
3062
+ const sigTable = new import_docx.Table({
3063
+ width: { size: 100, type: import_docx.WidthType.PERCENTAGE },
3064
+ columnWidths: colWidths,
3065
+ borders: {
3066
+ top: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
3067
+ bottom: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
3068
+ left: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
3069
+ right: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
3070
+ insideHorizontal: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
3071
+ insideVertical: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" }
3072
+ },
3073
+ rows: [
3074
+ new import_docx.TableRow({
3075
+ cantSplit: true,
3076
+ children: sigCells
3077
+ })
3078
+ ]
3079
+ });
3080
+ docElements.push(sigTable);
3081
+ }
2829
3082
  const contentWidthTwip = Math.max(
2830
3083
  1e3,
2831
3084
  resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
@@ -3056,6 +3309,140 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3056
3309
  });
3057
3310
  return await import_docx.Packer.toBuffer(document);
3058
3311
  }
3312
+ async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
3313
+ const cellParagraphs = [];
3314
+ if (item.title) {
3315
+ cellParagraphs.push(
3316
+ new import_docx.Paragraph({
3317
+ children: [
3318
+ new import_docx.TextRun({
3319
+ text: item.title,
3320
+ font: defaultFont,
3321
+ size: 18,
3322
+ // 9pt
3323
+ color: sig.titleColor.replace("#", ""),
3324
+ bold: true
3325
+ })
3326
+ ],
3327
+ spacing: { after: 60 }
3328
+ })
3329
+ );
3330
+ }
3331
+ if (item.image) {
3332
+ const resolvedImg = await resolveImage(item.image, baseDir);
3333
+ if (resolvedImg) {
3334
+ cellParagraphs.push(
3335
+ new import_docx.Paragraph({
3336
+ children: [
3337
+ new import_docx.ImageRun({
3338
+ data: resolvedImg.buffer,
3339
+ transformation: {
3340
+ width: 140,
3341
+ height: 60
3342
+ },
3343
+ type: "png"
3344
+ })
3345
+ ],
3346
+ spacing: { before: 40, after: 40 }
3347
+ })
3348
+ );
3349
+ } else {
3350
+ cellParagraphs.push(new import_docx.Paragraph({ spacing: { before: 240, after: 240 } }));
3351
+ }
3352
+ } else {
3353
+ cellParagraphs.push(new import_docx.Paragraph({ spacing: { before: 240, after: 240 } }));
3354
+ }
3355
+ if (sig.style === "line") {
3356
+ cellParagraphs.push(
3357
+ new import_docx.Paragraph({
3358
+ border: {
3359
+ bottom: {
3360
+ style: import_docx.BorderStyle.SINGLE,
3361
+ size: 6,
3362
+ space: 2,
3363
+ color: sig.borderColor.replace("#", "")
3364
+ }
3365
+ },
3366
+ spacing: { after: 60 }
3367
+ })
3368
+ );
3369
+ }
3370
+ cellParagraphs.push(
3371
+ new import_docx.Paragraph({
3372
+ children: [
3373
+ new import_docx.TextRun({
3374
+ text: item.name,
3375
+ font: defaultFont,
3376
+ size: 21,
3377
+ // 10.5pt
3378
+ bold: true,
3379
+ color: sig.nameColor.replace("#", "")
3380
+ })
3381
+ ],
3382
+ spacing: { before: sig.style === "line" ? 40 : 20, after: 20 }
3383
+ })
3384
+ );
3385
+ if (item.role) {
3386
+ cellParagraphs.push(
3387
+ new import_docx.Paragraph({
3388
+ children: [
3389
+ new import_docx.TextRun({
3390
+ text: item.role,
3391
+ font: defaultFont,
3392
+ size: 18,
3393
+ // 9pt
3394
+ color: sig.roleColor.replace("#", "")
3395
+ })
3396
+ ],
3397
+ spacing: { after: 20 }
3398
+ })
3399
+ );
3400
+ }
3401
+ if (item.date) {
3402
+ cellParagraphs.push(
3403
+ new import_docx.Paragraph({
3404
+ children: [
3405
+ new import_docx.TextRun({
3406
+ text: `Date: ${item.date}`,
3407
+ font: defaultFont,
3408
+ size: 17,
3409
+ // 8.5pt
3410
+ color: sig.roleColor.replace("#", "")
3411
+ })
3412
+ ],
3413
+ spacing: { after: 20 }
3414
+ })
3415
+ );
3416
+ }
3417
+ const isBox = sig.style === "box";
3418
+ const boxBorder = { style: import_docx.BorderStyle.SINGLE, size: 4, color: sig.borderColor.replace("#", "") };
3419
+ const noneBorder = { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" };
3420
+ return new import_docx.TableCell({
3421
+ width: { size: widthDxa, type: import_docx.WidthType.DXA },
3422
+ shading: isBox ? { fill: "F8FAFC", type: import_docx.ShadingType.CLEAR } : void 0,
3423
+ margins: isBox ? { top: 140, bottom: 140, left: 160, right: 160 } : { top: 60, bottom: 60, left: 60, right: 60 },
3424
+ borders: {
3425
+ top: isBox ? boxBorder : noneBorder,
3426
+ bottom: isBox ? boxBorder : noneBorder,
3427
+ left: isBox ? boxBorder : noneBorder,
3428
+ right: isBox ? boxBorder : noneBorder
3429
+ },
3430
+ children: cellParagraphs
3431
+ });
3432
+ }
3433
+ function createEmptyDocxCell(widthDxa) {
3434
+ const noneBorder = { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" };
3435
+ return new import_docx.TableCell({
3436
+ width: { size: widthDxa, type: import_docx.WidthType.DXA },
3437
+ borders: {
3438
+ top: noneBorder,
3439
+ bottom: noneBorder,
3440
+ left: noneBorder,
3441
+ right: noneBorder
3442
+ },
3443
+ children: [new import_docx.Paragraph({})]
3444
+ });
3445
+ }
3059
3446
 
3060
3447
  // src/core/engine.ts
3061
3448
  function formatServerTimestamp(date = /* @__PURE__ */ new Date()) {
@@ -3179,7 +3566,7 @@ try {
3179
3566
  }
3180
3567
  } catch {
3181
3568
  }
3182
- var FALLBACK_VERSION = "0.2.2";
3569
+ var FALLBACK_VERSION = "0.3.0";
3183
3570
  function readVersionFromPackageJson(fromDir) {
3184
3571
  let currentDir = fromDir;
3185
3572
  for (let i = 0; i < 6; i++) {
@@ -3246,6 +3633,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3246
3633
  markforge,
3247
3634
  normalizeHeaderFooter,
3248
3635
  normalizeHeaderFooterSlot,
3636
+ normalizeSignatures,
3249
3637
  normalizeWatermark,
3250
3638
  parseInlineSpans,
3251
3639
  parseMarginToTwip,