@masumdev/markforge 0.3.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.
package/dist/index.mjs CHANGED
@@ -49,6 +49,26 @@ function parseInlineSpans(text) {
49
49
  remaining = remaining.slice(imgMatch[0].length);
50
50
  continue;
51
51
  }
52
+ const fnMatch = remaining.match(/^\[\^([\w-]+)\]/);
53
+ if (fnMatch) {
54
+ const fnId = fnMatch[1];
55
+ spans.push({
56
+ type: "footnoteRef",
57
+ content: fnId,
58
+ footnoteId: fnId
59
+ });
60
+ remaining = remaining.slice(fnMatch[0].length);
61
+ continue;
62
+ }
63
+ const mathMatch = remaining.match(/^\$([^$\n]+?)\$/);
64
+ if (mathMatch && !mathMatch[1].startsWith("$")) {
65
+ spans.push({
66
+ type: "mathInline",
67
+ content: mathMatch[1]
68
+ });
69
+ remaining = remaining.slice(mathMatch[0].length);
70
+ continue;
71
+ }
52
72
  const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
53
73
  if (linkMatch) {
54
74
  spans.push({
@@ -148,7 +168,7 @@ function parseInlineSpans(text) {
148
168
  remaining = remaining.slice(fullTag.length);
149
169
  continue;
150
170
  }
151
- const nextSpecial = remaining.search(/[\*\_\[\!`~<]/);
171
+ const nextSpecial = remaining.search(/[\*\_\[\!`~<\$]/);
152
172
  if (nextSpecial === -1) {
153
173
  spans.push({
154
174
  type: "text",
@@ -169,11 +189,51 @@ function parseInlineSpans(text) {
169
189
  remaining = remaining.slice(nextSpecial);
170
190
  }
171
191
  }
172
- 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;
173
202
  }
174
203
  function slugify(text) {
175
204
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
176
205
  }
206
+ function applyHeadingNumbering(nodes, tocEntries, options) {
207
+ const depth = options.depth ?? 3;
208
+ const skipH1 = options.skipH1 ?? false;
209
+ const prefix = options.prefix ?? "";
210
+ const counters = [0, 0, 0, 0, 0, 0];
211
+ for (const node of nodes) {
212
+ if (node.type === "heading" && node.level) {
213
+ if (node._numbered) continue;
214
+ const lvl = node.level;
215
+ if (lvl > depth) continue;
216
+ if (lvl === 1 && skipH1) continue;
217
+ const idx = lvl - 1;
218
+ counters[idx]++;
219
+ for (let c = idx + 1; c < counters.length; c++) {
220
+ counters[c] = 0;
221
+ }
222
+ const startIdx = skipH1 ? 1 : 0;
223
+ const parts = counters.slice(startIdx, idx + 1).filter((n) => n > 0);
224
+ const numberStr = parts.join(".") + ".";
225
+ const fullPrefix = prefix ? `${prefix} ${numberStr} ` : `${numberStr} `;
226
+ const originalText = node.text || "";
227
+ node.text = fullPrefix + originalText;
228
+ node.inlines = parseInlineSpans(node.text);
229
+ node._numbered = true;
230
+ const toc = tocEntries.find((t) => t.id === node.id);
231
+ if (toc) {
232
+ toc.text = node.text;
233
+ }
234
+ }
235
+ }
236
+ }
177
237
  function parseMarkdownDocument(rawMarkdown) {
178
238
  const { data: frontmatter, content } = matter(rawMarkdown);
179
239
  const metadata = frontmatter || {};
@@ -185,6 +245,7 @@ function parseMarkdownDocument(rawMarkdown) {
185
245
  const lines = cleanContent.split(/\r?\n/);
186
246
  const nodes = [];
187
247
  const tocEntries = [];
248
+ const footnoteDefs = [];
188
249
  let i = 0;
189
250
  while (i < lines.length) {
190
251
  const line = lines[i];
@@ -213,6 +274,29 @@ function parseMarkdownDocument(rawMarkdown) {
213
274
  i++;
214
275
  continue;
215
276
  }
277
+ if (line.trim().startsWith("$$")) {
278
+ const mathLines = [];
279
+ const singleLine = line.trim().match(/^\$\$(.+)\$\$$/);
280
+ if (singleLine) {
281
+ nodes.push({
282
+ type: "mathBlock",
283
+ text: singleLine[1].trim()
284
+ });
285
+ i++;
286
+ continue;
287
+ }
288
+ i++;
289
+ while (i < lines.length && !lines[i].trim().startsWith("$$")) {
290
+ mathLines.push(lines[i]);
291
+ i++;
292
+ }
293
+ if (i < lines.length) i++;
294
+ nodes.push({
295
+ type: "mathBlock",
296
+ text: mathLines.join("\n").trim()
297
+ });
298
+ continue;
299
+ }
216
300
  const codeBlockMatch = line.match(/^```(\w+)?/);
217
301
  if (codeBlockMatch) {
218
302
  const language = (codeBlockMatch[1] || "text").trim().toLowerCase();
@@ -239,6 +323,100 @@ function parseMarkdownDocument(rawMarkdown) {
239
323
  }
240
324
  continue;
241
325
  }
326
+ const colsMatch = line.trim().match(/^:::columns(?:\s+\[?([\w\s=.-]+)\]?)?$/i);
327
+ if (colsMatch) {
328
+ const attrStr = colsMatch[1] || "";
329
+ let colsCount = 2;
330
+ let colGap = "1.5rem";
331
+ if (attrStr) {
332
+ const numMatch = attrStr.trim().match(/^(\d+)$/);
333
+ const cMatch = attrStr.match(/cols=(\d+)/i) || attrStr.match(/columns=(\d+)/i);
334
+ const gMatch = attrStr.match(/gap=([^\s]+)/i);
335
+ if (numMatch) colsCount = parseInt(numMatch[1], 10);
336
+ else if (cMatch) colsCount = parseInt(cMatch[1], 10);
337
+ if (gMatch) colGap = gMatch[1];
338
+ }
339
+ const columnNodes = [];
340
+ let currentColumnLines = [];
341
+ let inColBlock = false;
342
+ i++;
343
+ while (i < lines.length) {
344
+ const curLine = lines[i];
345
+ const trimmed = curLine.trim();
346
+ if (/^:::col(?:umn)?$/i.test(trimmed)) {
347
+ if (currentColumnLines.length > 0) {
348
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
349
+ columnNodes.push({
350
+ type: "column",
351
+ children: subDoc.nodes
352
+ });
353
+ currentColumnLines = [];
354
+ }
355
+ inColBlock = true;
356
+ i++;
357
+ } else if (trimmed === ":::") {
358
+ if (inColBlock) {
359
+ if (currentColumnLines.length > 0) {
360
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
361
+ columnNodes.push({
362
+ type: "column",
363
+ children: subDoc.nodes
364
+ });
365
+ currentColumnLines = [];
366
+ }
367
+ inColBlock = false;
368
+ i++;
369
+ } else {
370
+ if (currentColumnLines.length > 0) {
371
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
372
+ columnNodes.push({
373
+ type: "column",
374
+ children: subDoc.nodes
375
+ });
376
+ currentColumnLines = [];
377
+ }
378
+ i++;
379
+ break;
380
+ }
381
+ } else {
382
+ currentColumnLines.push(curLine);
383
+ i++;
384
+ }
385
+ }
386
+ if (currentColumnLines.length > 0) {
387
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
388
+ columnNodes.push({
389
+ type: "column",
390
+ children: subDoc.nodes
391
+ });
392
+ }
393
+ nodes.push({
394
+ type: "columns",
395
+ columnsCount: columnNodes.length > 0 ? columnNodes.length : colsCount,
396
+ columnGap: colGap,
397
+ children: columnNodes
398
+ });
399
+ continue;
400
+ }
401
+ const fnDefMatch = line.match(/^\[\^([\w-]+)\]:\s+(.+)$/);
402
+ if (fnDefMatch) {
403
+ const fnId = fnDefMatch[1];
404
+ const fnText = fnDefMatch[2].trim();
405
+ const inlines = parseInlineSpans(fnText);
406
+ footnoteDefs.push({
407
+ id: fnId,
408
+ text: fnText,
409
+ inlines
410
+ });
411
+ nodes.push({
412
+ type: "footnoteDef",
413
+ footnoteId: fnId,
414
+ text: fnText,
415
+ inlines
416
+ });
417
+ i++;
418
+ continue;
419
+ }
242
420
  const calloutMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i);
243
421
  if (calloutMatch) {
244
422
  const calloutType = calloutMatch[1].toUpperCase();
@@ -362,7 +540,7 @@ function parseMarkdownDocument(rawMarkdown) {
362
540
  continue;
363
541
  }
364
542
  const paraLines = [];
365
- while (i < lines.length && lines[i].trim() && !lines[i].match(/^#{1,6}\s+/) && !lines[i].startsWith("```") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !lines[i].match(/^(\s*)([-*+]|\d+\.)\s+/)) {
543
+ while (i < lines.length && lines[i].trim() && !lines[i].match(/^#{1,6}\s+/) && !lines[i].startsWith("```") && !lines[i].startsWith("$$") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !lines[i].match(/^:::columns/) && !lines[i].match(/^\[\^[\w-]+\]:\s+/) && !lines[i].match(/^(\s*)([-*+]|\d+\.)\s+/)) {
366
544
  paraLines.push(lines[i]);
367
545
  i++;
368
546
  }
@@ -373,12 +551,19 @@ function parseMarkdownDocument(rawMarkdown) {
373
551
  inlines: parseInlineSpans(paraText)
374
552
  });
375
553
  }
554
+ if (metadata.numberHeadings) {
555
+ const opts = typeof metadata.numberHeadings === "boolean" ? { enabled: metadata.numberHeadings } : metadata.numberHeadings;
556
+ if (opts.enabled !== false) {
557
+ applyHeadingNumbering(nodes, tocEntries, opts);
558
+ }
559
+ }
376
560
  return {
377
561
  metadata,
378
562
  content: cleanContent,
379
563
  nodes,
380
564
  tocEntries,
381
- inlinedStyles
565
+ inlinedStyles,
566
+ footnoteDefs
382
567
  };
383
568
  }
384
569
 
@@ -404,7 +589,9 @@ import {
404
589
  convertMillimetersToTwip,
405
590
  ShadingType,
406
591
  ExternalHyperlink,
407
- TabStopType
592
+ TabStopType,
593
+ PageBreak,
594
+ NumberFormat
408
595
  } from "docx";
409
596
 
410
597
  // src/core/imageResolver.ts
@@ -466,9 +653,14 @@ async function resolveImage(src, baseDir = process.cwd()) {
466
653
  memoryImageCache.set(cacheKey, resolved2);
467
654
  return resolved2;
468
655
  }
469
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
656
+ let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
470
657
  if (!fs.existsSync(localPath)) {
471
- return null;
658
+ const cwdPath = path.resolve(process.cwd(), src);
659
+ if (fs.existsSync(cwdPath)) {
660
+ localPath = cwdPath;
661
+ } else {
662
+ return null;
663
+ }
472
664
  }
473
665
  const buffer = fs.readFileSync(localPath);
474
666
  const mimeType = getMimeType(localPath);
@@ -873,6 +1065,8 @@ import * as path4 from "path";
873
1065
  import * as os from "os";
874
1066
  import { pathToFileURL as pathToFileURL2 } from "url";
875
1067
  import { spawnSync } from "child_process";
1068
+ import { PDFDocument } from "pdf-lib";
1069
+ import { encryptPDF } from "@pdfsmaller/pdf-encrypt";
876
1070
 
877
1071
  // src/core/html/htmlBuilder.ts
878
1072
  import * as fs3 from "fs";
@@ -944,7 +1138,7 @@ var THEME_COMPONENTS = `
944
1138
  .document-meta { font-size: 0.9rem; color: var(--mf-text-muted); display: flex; gap: 1.5rem; flex-wrap: wrap; }
945
1139
 
946
1140
  /* Table of Contents */
947
- .table-of-contents { background: var(--mf-card-bg); border: 1px solid var(--mf-border); border-radius: 8px; padding: 1.5rem 2rem; margin: 2rem 0; }
1141
+ .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; }
948
1142
  .table-of-contents h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--mf-text-muted); margin: 0 0 1rem 0; }
949
1143
  .table-of-contents ul { list-style: none; padding: 0; margin: 0; }
950
1144
  .table-of-contents li { padding: 0.25rem 0; }
@@ -1017,11 +1211,13 @@ var THEME_CORPORATE = `
1017
1211
  }
1018
1212
  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; }
1019
1213
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1020
- h1, h2, h3, h4, h5, h6 { color: var(--mf-text); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1021
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1214
+ 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; }
1215
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
1022
1216
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
1023
- h3 { font-size: 1.3rem; }
1024
- h4 { font-size: 1.1rem; }
1217
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
1218
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
1219
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
1220
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
1025
1221
  p { margin: 0.8rem 0; }
1026
1222
  `;
1027
1223
  var THEME_DEFAULT = THEME_CORPORATE;
@@ -1063,11 +1259,13 @@ function generateThemeCss(theme) {
1063
1259
  }
1064
1260
  body { background-color: var(--mf-bg); color: var(--mf-text); font-family: var(--mf-font-family); font-size: 15px; line-height: 1.65; margin: 0; padding: 2.5rem; }
1065
1261
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1066
- h1, h2, h3, h4, h5, h6 { color: var(--mf-text); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1067
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1262
+ 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; }
1263
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
1068
1264
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid var(--mf-border); padding-bottom: 0.4rem; }
1069
- h3 { font-size: 1.3rem; }
1070
- h4 { font-size: 1.1rem; }
1265
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
1266
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
1267
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
1268
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
1071
1269
  p { margin: 0.8rem 0; }
1072
1270
  ${theme.customCss || ""}
1073
1271
  `;
@@ -1115,6 +1313,12 @@ var DEFAULT_CONFIG = {
1115
1313
  },
1116
1314
  toc: false,
1117
1315
  watermark: void 0,
1316
+ signatures: void 0,
1317
+ coverPage: void 0,
1318
+ backCover: void 0,
1319
+ numberHeadings: void 0,
1320
+ security: void 0,
1321
+ math: true,
1118
1322
  embedImages: true,
1119
1323
  metadata: void 0,
1120
1324
  watch: false,
@@ -1270,15 +1474,45 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1270
1474
  if (/^[0-9.]+$/.test(str)) return `${str}pt`;
1271
1475
  return str;
1272
1476
  }
1273
- function replaceDocumentTokens(template = "", meta) {
1274
- return template.replace(/\{title\}/gi, meta.title || "").replace(/\{subtitle\}/gi, meta.subtitle || "").replace(/\{author\}/gi, meta.author || "").replace(/\{version\}/gi, meta.version || "").replace(/\{date\}/gi, meta.date || "").replace(/\{company\}/gi, meta.company || "");
1477
+ function replaceDocumentTokens(template = "", meta = {}) {
1478
+ if (!template) return "";
1479
+ const currentYear = meta.year ? String(meta.year) : (/* @__PURE__ */ new Date()).getFullYear().toString();
1480
+ const tokenMap = {
1481
+ title: meta.title ? String(meta.title) : "",
1482
+ subtitle: meta.subtitle ? String(meta.subtitle) : "",
1483
+ author: meta.author ? Array.isArray(meta.author) ? meta.author.join(", ") : String(meta.author) : "",
1484
+ version: meta.version ? String(meta.version) : "",
1485
+ date: meta.date ? String(meta.date) : "",
1486
+ company: meta.company ? String(meta.company) : "",
1487
+ year: currentYear
1488
+ };
1489
+ if (meta.metadata && typeof meta.metadata === "object") {
1490
+ for (const [key, val] of Object.entries(meta.metadata)) {
1491
+ if (val !== void 0 && val !== null) {
1492
+ tokenMap[key.toLowerCase()] = String(val);
1493
+ }
1494
+ }
1495
+ }
1496
+ for (const [key, val] of Object.entries(meta)) {
1497
+ if (val !== void 0 && val !== null && typeof val !== "object") {
1498
+ tokenMap[key.toLowerCase()] = String(val);
1499
+ }
1500
+ }
1501
+ return template.replace(/\{([a-zA-Z0-9_\-]+)\}/gi, (match, tokenKey) => {
1502
+ const lowerKey = tokenKey.toLowerCase();
1503
+ if (lowerKey in tokenMap) {
1504
+ return tokenMap[lowerKey];
1505
+ }
1506
+ return match;
1507
+ });
1275
1508
  }
1276
- function normalizeWatermark(rawWatermark) {
1509
+ function normalizeWatermark(rawWatermark, tokens) {
1277
1510
  if (!rawWatermark) {
1278
1511
  return void 0;
1279
1512
  }
1280
1513
  if (typeof rawWatermark === "string") {
1281
- const text = rawWatermark.trim();
1514
+ let text = rawWatermark.trim();
1515
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1282
1516
  if (!text) return void 0;
1283
1517
  return {
1284
1518
  text,
@@ -1291,8 +1525,10 @@ function normalizeWatermark(rawWatermark) {
1291
1525
  }
1292
1526
  if (typeof rawWatermark === "object") {
1293
1527
  if (!rawWatermark.text || !rawWatermark.text.trim()) return void 0;
1528
+ let text = rawWatermark.text.trim();
1529
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1294
1530
  return {
1295
- text: rawWatermark.text.trim(),
1531
+ text,
1296
1532
  color: rawWatermark.color || "#94a3b8",
1297
1533
  opacity: typeof rawWatermark.opacity === "number" ? rawWatermark.opacity : 0.08,
1298
1534
  fontSize: rawWatermark.fontSize || 54,
@@ -1408,6 +1644,174 @@ function normalizeSignatures(raw, meta = {}) {
1408
1644
  spacingBeforeTwip
1409
1645
  };
1410
1646
  }
1647
+ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1648
+ if (!rawCover) return void 0;
1649
+ const cfg = typeof rawCover === "object" ? rawCover : {};
1650
+ if (cfg.enabled === false) return void 0;
1651
+ const preset = cfg.preset || "modern";
1652
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title ? String(tokenCtx.title) : "Document Title";
1653
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle ? String(tokenCtx.subtitle) : void 0;
1654
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1655
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1656
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1657
+ let dateStr;
1658
+ if (typeof cfg.date === "string") {
1659
+ dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
1660
+ } else if (cfg.date === true) {
1661
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1662
+ } else {
1663
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : void 0;
1664
+ }
1665
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1666
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1667
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1668
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1669
+ const logoWidth = cfg.logoWidth;
1670
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1671
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1672
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1673
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1674
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1675
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1676
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1677
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1678
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1679
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1680
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1681
+ let socialMap;
1682
+ if (cfg.social && typeof cfg.social === "object") {
1683
+ socialMap = {};
1684
+ for (const [k, v] of Object.entries(cfg.social)) {
1685
+ if (typeof v === "string") {
1686
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1687
+ }
1688
+ }
1689
+ }
1690
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1691
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : void 0;
1692
+ return {
1693
+ enabled: true,
1694
+ preset,
1695
+ title,
1696
+ subtitle,
1697
+ author,
1698
+ company,
1699
+ version,
1700
+ date: dateStr,
1701
+ badge,
1702
+ badgeColor,
1703
+ badgeTextColor,
1704
+ logo,
1705
+ logoWidth,
1706
+ bgGradient,
1707
+ backgroundColor,
1708
+ textColor,
1709
+ titleColor,
1710
+ subtitleColor,
1711
+ accentColor,
1712
+ footerText,
1713
+ address,
1714
+ email,
1715
+ phone,
1716
+ website,
1717
+ social: socialMap,
1718
+ copyright
1719
+ };
1720
+ }
1721
+ function normalizeBackCover(rawBack, tokenCtx = {}) {
1722
+ if (!rawBack) return void 0;
1723
+ const cfg = typeof rawBack === "object" ? rawBack : {};
1724
+ if (cfg.enabled === false) return void 0;
1725
+ const preset = cfg.preset || "modern";
1726
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
1727
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
1728
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1729
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1730
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1731
+ const date = typeof cfg.date === "string" ? replaceDocumentTokens(cfg.date, tokenCtx) : tokenCtx.date ? String(tokenCtx.date) : void 0;
1732
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1733
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1734
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1735
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1736
+ let socialMap;
1737
+ if (cfg.social && typeof cfg.social === "object") {
1738
+ socialMap = {};
1739
+ for (const [k, v] of Object.entries(cfg.social)) {
1740
+ if (typeof v === "string") {
1741
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1742
+ }
1743
+ }
1744
+ }
1745
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1746
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
1747
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1748
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1749
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1750
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1751
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1752
+ const logoWidth = cfg.logoWidth;
1753
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1754
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1755
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1756
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1757
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1758
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1759
+ return {
1760
+ enabled: true,
1761
+ preset,
1762
+ title,
1763
+ subtitle,
1764
+ author,
1765
+ company,
1766
+ version,
1767
+ date,
1768
+ address,
1769
+ email,
1770
+ phone,
1771
+ website,
1772
+ social: socialMap,
1773
+ copyright,
1774
+ footerText,
1775
+ badge,
1776
+ badgeColor,
1777
+ badgeTextColor,
1778
+ logo,
1779
+ logoWidth,
1780
+ bgGradient,
1781
+ backgroundColor,
1782
+ textColor,
1783
+ titleColor,
1784
+ subtitleColor,
1785
+ accentColor
1786
+ };
1787
+ }
1788
+ function normalizeNumberHeadings(raw) {
1789
+ if (raw === void 0 || raw === false) return void 0;
1790
+ if (raw === true) {
1791
+ return { enabled: true, depth: 3, skipH1: false, prefix: "" };
1792
+ }
1793
+ if (typeof raw === "object") {
1794
+ const obj = raw;
1795
+ if (obj.enabled === false) return void 0;
1796
+ return {
1797
+ enabled: true,
1798
+ depth: obj.depth ?? 3,
1799
+ skipH1: obj.skipH1 ?? false,
1800
+ prefix: obj.prefix ?? ""
1801
+ };
1802
+ }
1803
+ return void 0;
1804
+ }
1805
+ function normalizeSecurity(raw) {
1806
+ if (!raw) return void 0;
1807
+ const sec = raw;
1808
+ if (!sec.userPassword && !sec.ownerPassword && !sec.permissions) return void 0;
1809
+ return {
1810
+ userPassword: sec.userPassword,
1811
+ ownerPassword: sec.ownerPassword,
1812
+ permissions: sec.permissions
1813
+ };
1814
+ }
1411
1815
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1412
1816
  const configMeta = userConfig.metadata || {};
1413
1817
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1418,7 +1822,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1418
1822
  const version = mergedMeta.version || void 0;
1419
1823
  const company = mergedMeta.company || void 0;
1420
1824
  const lang = mergedMeta.lang || "en";
1421
- const tokenContext = { title, subtitle, author, version, date, company };
1825
+ const tokenContext = {
1826
+ ...mergedMeta,
1827
+ title,
1828
+ subtitle,
1829
+ author,
1830
+ version,
1831
+ date,
1832
+ company
1833
+ };
1422
1834
  const theme = mergedMeta.theme || userConfig.theme || DEFAULT_CONFIG.theme;
1423
1835
  const orientation = mergedMeta.orientation || userConfig.orientation || DEFAULT_CONFIG.orientation;
1424
1836
  const paperSize = mergedMeta.paperSize || userConfig.paperSize || DEFAULT_CONFIG.paperSize;
@@ -1447,9 +1859,18 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1447
1859
  const footer = normalizeHeaderFooter(rawFooter, tokenContext);
1448
1860
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1449
1861
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1450
- const watermark = normalizeWatermark(rawWatermark);
1862
+ const watermark = normalizeWatermark(rawWatermark, tokenContext);
1451
1863
  const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1452
1864
  const signatures = normalizeSignatures(rawSignatures, tokenContext);
1865
+ const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
1866
+ const coverPage = normalizeCoverPage(rawCover, tokenContext);
1867
+ const rawBack = mergedMeta.backCover !== void 0 ? mergedMeta.backCover : userConfig.backCover;
1868
+ const backCover = normalizeBackCover(rawBack, tokenContext);
1869
+ const rawNumberHeadings = mergedMeta.numberHeadings !== void 0 ? mergedMeta.numberHeadings : userConfig.numberHeadings;
1870
+ const numberHeadings = normalizeNumberHeadings(rawNumberHeadings);
1871
+ const rawSecurity = mergedMeta.security || userConfig.security;
1872
+ const security = normalizeSecurity(rawSecurity);
1873
+ const math = mergedMeta.math !== false && userConfig.math !== false;
1453
1874
  const cssList = [];
1454
1875
  const addCss = (item) => {
1455
1876
  if (!item) return;
@@ -1479,6 +1900,11 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1479
1900
  toc,
1480
1901
  signatures,
1481
1902
  watermark,
1903
+ coverPage,
1904
+ backCover,
1905
+ numberHeadings,
1906
+ security,
1907
+ math,
1482
1908
  css: cssList,
1483
1909
  embedImages,
1484
1910
  bundleHtml,
@@ -1486,11 +1912,50 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1486
1912
  };
1487
1913
  }
1488
1914
 
1915
+ // src/core/math/mathRenderer.ts
1916
+ import katex from "katex";
1917
+ function renderMathToHtml(latex, displayMode = false) {
1918
+ try {
1919
+ return katex.renderToString(latex.trim(), {
1920
+ displayMode,
1921
+ throwOnError: false,
1922
+ output: "htmlAndMathml",
1923
+ strict: false
1924
+ });
1925
+ } catch {
1926
+ return `<span class="katex-fallback">${latex}</span>`;
1927
+ }
1928
+ }
1929
+ var KATEX_INLINE_CSS = `
1930
+ .katex { font: normal 1.21em KaTeX_Main, Times New Roman, serif; line-height: 1.2; text-indent: 0; text-rendering: auto; border-color: currentColor; }
1931
+ .katex * { -ms-high-contrast-adjust: none !important; }
1932
+ .katex .katex-html { display: inline-block; }
1933
+ .katex .katex-mathml { clip: rect(1px, 1px, 1px, 1px); border: 0; height: 1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
1934
+ .katex-display { display: block; margin: 1em 0; text-align: center; }
1935
+ .katex-display > .katex { display: inline-block; text-align: initial; }
1936
+ .katex .base { position: relative; white-space: nowrap; width: min-content; }
1937
+ .katex .strut { display: inline-block; }
1938
+ .katex .mord { display: inline-block; }
1939
+ .katex .mbin { display: inline-block; }
1940
+ .katex .mrel { display: inline-block; }
1941
+ .katex .mopen { display: inline-block; }
1942
+ .katex .mclose { display: inline-block; }
1943
+ .katex .mpunct { display: inline-block; }
1944
+ .katex .minner { display: inline-block; }
1945
+ .katex .mop { display: inline-block; }
1946
+ .katex .frac-line { width: 100%; border-bottom-style: solid; }
1947
+ .katex .vlist-t { display: inline-table; table-layout: fixed; }
1948
+ .katex .vlist-r { display: table-row; }
1949
+ .katex .vlist { display: table-cell; vertical-align: bottom; position: relative; }
1950
+ .katex .msupsub { text-align: left; }
1951
+ .katex .sqrt > .root { margin-left: 0.27777778em; margin-right: -0.55555556em; }
1952
+ `;
1953
+
1489
1954
  // src/core/html/htmlBuilder.ts
1490
1955
  function escapeHtml(str) {
1491
1956
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1492
1957
  }
1493
- async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1958
+ async function renderInlinesToHtml(spans = [], baseDir = process.cwd(), tokens) {
1494
1959
  let result = "";
1495
1960
  for (const span of spans) {
1496
1961
  if (span.type === "image" && span.url) {
@@ -1504,23 +1969,23 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1504
1969
  continue;
1505
1970
  }
1506
1971
  if (span.type === "link" && span.url) {
1507
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1972
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1508
1973
  const title = span.title ? ` title="${escapeHtml(span.title)}"` : "";
1509
1974
  result += `<a href="${escapeHtml(span.url)}"${title}>${inner}</a>`;
1510
1975
  continue;
1511
1976
  }
1512
1977
  if (span.type === "bold") {
1513
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1978
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1514
1979
  result += `<strong>${inner}</strong>`;
1515
1980
  continue;
1516
1981
  }
1517
1982
  if (span.type === "italic") {
1518
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1983
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1519
1984
  result += `<em>${inner}</em>`;
1520
1985
  continue;
1521
1986
  }
1522
1987
  if (span.type === "strikethrough") {
1523
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
1988
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1524
1989
  result += `<del>${inner}</del>`;
1525
1990
  continue;
1526
1991
  }
@@ -1528,83 +1993,57 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1528
1993
  result += `<code>${escapeHtml(span.content)}</code>`;
1529
1994
  continue;
1530
1995
  }
1996
+ if (span.type === "mathInline") {
1997
+ result += renderMathToHtml(span.content, false);
1998
+ continue;
1999
+ }
2000
+ if (span.type === "footnoteRef") {
2001
+ const id = escapeHtml(span.footnoteId || span.content);
2002
+ result += `<sup><a href="#fn-${id}" id="fnref-${id}" class="markforge-fnref">[${escapeHtml(span.content)}]</a></sup>`;
2003
+ continue;
2004
+ }
1531
2005
  if (span.type === "htmlInline") {
1532
2006
  result += span.content;
1533
2007
  continue;
1534
2008
  }
1535
- result += escapeHtml(span.content);
2009
+ const content = tokens ? replaceDocumentTokens(span.content, tokens) : span.content;
2010
+ result += escapeHtml(content);
1536
2011
  }
1537
2012
  return result;
1538
2013
  }
1539
- async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1540
- const resolved = resolveDocumentConfig(doc.metadata, config);
1541
- const baseThemeCss = generateThemeCss(resolved.theme);
1542
- let customCss = "";
1543
- for (const cssPath of resolved.css) {
1544
- const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
1545
- if (fs3.existsSync(fullCssPath)) {
1546
- customCss += `
1547
- /* Custom CSS: ${cssPath} */
1548
- ` + fs3.readFileSync(fullCssPath, "utf-8");
1549
- }
1550
- }
1551
- const inlinedCss = doc.inlinedStyles.join("\n");
2014
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd(), tokens) {
1552
2015
  let bodyHtml = "";
1553
- if (resolved.title) {
1554
- bodyHtml += ` <header class="document-header">
1555
- `;
1556
- bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1557
- `;
1558
- if (resolved.subtitle) {
1559
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1560
- `;
1561
- }
1562
- if (resolved.author || resolved.date || resolved.version) {
1563
- bodyHtml += ` <div class="document-meta">
1564
- `;
1565
- if (resolved.author) {
1566
- bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1567
- `;
1568
- }
1569
- if (resolved.version) {
1570
- bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1571
- `;
1572
- }
1573
- if (resolved.date) {
1574
- bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1575
- `;
1576
- }
1577
- bodyHtml += ` </div>
1578
- `;
1579
- }
1580
- bodyHtml += ` </header>
1581
- `;
1582
- }
1583
- if (resolved.toc && doc.tocEntries.length > 0) {
1584
- bodyHtml += ` <nav class="table-of-contents">
1585
- `;
1586
- bodyHtml += ` <h2>Table of Contents</h2>
1587
- <ul>
1588
- `;
1589
- for (const entry of doc.tocEntries) {
1590
- const indent = " ".repeat(entry.level);
1591
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1592
- `;
1593
- }
1594
- bodyHtml += ` </ul>
1595
- </nav>
1596
- `;
1597
- }
1598
- for (const node of doc.nodes) {
2016
+ const tokenCtx = tokens || resolved;
2017
+ for (const node of nodes) {
1599
2018
  if (node.type === "heading") {
1600
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2019
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1601
2020
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
1602
2021
  `;
1603
2022
  continue;
1604
2023
  }
1605
2024
  if (node.type === "paragraph") {
1606
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2025
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1607
2026
  bodyHtml += ` <p>${inner}</p>
2027
+ `;
2028
+ continue;
2029
+ }
2030
+ if (node.type === "mathBlock") {
2031
+ bodyHtml += ` <div class="math-block">${renderMathToHtml(node.text || "", true)}</div>
2032
+ `;
2033
+ continue;
2034
+ }
2035
+ if (node.type === "columns") {
2036
+ const cols = node.columnsCount || 2;
2037
+ const gap = node.columnGap || "1.5rem";
2038
+ let colChildrenHtml = "";
2039
+ for (const col of node.children || []) {
2040
+ const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir, tokenCtx);
2041
+ colChildrenHtml += ` <div class="markforge-col">
2042
+ ${colInner} </div>
2043
+ `;
2044
+ }
2045
+ bodyHtml += ` <div class="markforge-columns" style="--cols: ${cols}; --col-gap: ${gap};">
2046
+ ${colChildrenHtml} </div>
1608
2047
  `;
1609
2048
  continue;
1610
2049
  }
@@ -1624,7 +2063,7 @@ ${escapeHtml(node.text || "")}
1624
2063
  continue;
1625
2064
  }
1626
2065
  if (node.type === "callout") {
1627
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2066
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1628
2067
  const CALLOUT_STYLES = {
1629
2068
  NOTE: { bg: "#ECFDFD", border: "#33CDCF", titleColor: "#009DA0" },
1630
2069
  TIP: { bg: "#ecfdf5", border: "#10b981", titleColor: "#10b981" },
@@ -1644,7 +2083,7 @@ ${escapeHtml(node.text || "")}
1644
2083
  continue;
1645
2084
  }
1646
2085
  if (node.type === "blockquote") {
1647
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2086
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1648
2087
  bodyHtml += ` <blockquote>${inner}</blockquote>
1649
2088
  `;
1650
2089
  continue;
@@ -1658,7 +2097,7 @@ ${escapeHtml(node.text || "")}
1658
2097
  for (const cell of row.children || []) {
1659
2098
  const tag = cell.isHeader ? "th" : "td";
1660
2099
  const align = cell.align ? ` align="${cell.align}"` : "";
1661
- const inner = await renderInlinesToHtml(cell.inlines, baseDir);
2100
+ const inner = await renderInlinesToHtml(cell.inlines, baseDir, tokenCtx);
1662
2101
  bodyHtml += ` <${tag}${align}>${inner}</${tag}>
1663
2102
  `;
1664
2103
  }
@@ -1674,7 +2113,7 @@ ${escapeHtml(node.text || "")}
1674
2113
  bodyHtml += ` <${tag}>
1675
2114
  `;
1676
2115
  for (const item of node.children) {
1677
- const inner = await renderInlinesToHtml(item.inlines, baseDir);
2116
+ const inner = await renderInlinesToHtml(item.inlines, baseDir, tokenCtx);
1678
2117
  bodyHtml += ` <li>${inner}</li>
1679
2118
  `;
1680
2119
  }
@@ -1693,70 +2132,508 @@ ${escapeHtml(node.text || "")}
1693
2132
  continue;
1694
2133
  }
1695
2134
  }
1696
- let watermarkCss = "";
1697
- let watermarkHtml = "";
1698
- if (resolved.watermark) {
1699
- const wm = resolved.watermark;
1700
- watermarkCss = `
1701
- .document-watermark {
1702
- position: fixed;
1703
- top: 0;
1704
- left: 0;
1705
- width: 100vw;
1706
- height: 100vh;
1707
- pointer-events: none;
1708
- z-index: 0;
1709
- user-select: none;
1710
- -webkit-user-select: none;
2135
+ return bodyHtml;
2136
+ }
2137
+ async function renderCoverPageHtml(cover, baseDir = process.cwd()) {
2138
+ let logoHtml = "";
2139
+ if (cover.logo) {
2140
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
2141
+ const src = resolvedLogo ? resolvedLogo.dataUri : cover.logo;
2142
+ const widthStyle = cover.logoWidth ? `max-width: ${typeof cover.logoWidth === "number" ? cover.logoWidth + "px" : cover.logoWidth}; max-height: 80px; width: auto; height: auto;` : "max-height: 60px; max-width: 180px; width: auto; height: auto;";
2143
+ logoHtml = `<div class="cover-logo"><img src="${src}" alt="Logo" style="${widthStyle} object-fit: contain;" /></div>`;
1711
2144
  }
1712
- .document-container {
2145
+ const badgeHtml = cover.badge ? `<div class="cover-badge" style="${cover.badgeColor ? `background-color: ${cover.badgeColor};` : ""}${cover.badgeTextColor ? `color: ${cover.badgeTextColor};` : ""}">${escapeHtml(cover.badge)}</div>` : "";
2146
+ const titleHtml = `<h1 class="cover-title">${escapeHtml(cover.title)}</h1>`;
2147
+ const subtitleHtml = cover.subtitle ? `<div class="cover-subtitle">${escapeHtml(cover.subtitle)}</div>` : "";
2148
+ const metaItems = [];
2149
+ if (cover.company) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Organization:</span> <span class="cover-meta-value">${escapeHtml(cover.company)}</span></div>`);
2150
+ if (cover.author) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Author:</span> <span class="cover-meta-value">${escapeHtml(cover.author)}</span></div>`);
2151
+ if (cover.version) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Version:</span> <span class="cover-meta-value">${escapeHtml(cover.version)}</span></div>`);
2152
+ if (cover.date) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Date:</span> <span class="cover-meta-value">${escapeHtml(cover.date)}</span></div>`);
2153
+ const metaHtml = metaItems.length > 0 ? `<div class="cover-meta">${metaItems.join("\n")}</div>` : "";
2154
+ const footerHtml = cover.footerText ? `<div class="cover-footer-text">${escapeHtml(cover.footerText)}</div>` : "";
2155
+ const css = `
2156
+ .markforge-cover {
2157
+ min-height: 100vh;
2158
+ box-sizing: border-box;
2159
+ display: flex;
2160
+ flex-direction: column;
2161
+ justify-content: space-between;
2162
+ padding: 4rem 3.5rem;
2163
+ page-break-after: always;
2164
+ break-after: page;
1713
2165
  position: relative;
1714
- z-index: 1;
2166
+ z-index: 2;
2167
+ background: ${cover.bgGradient || "#FFFFFF"};
2168
+ -webkit-print-color-adjust: exact;
2169
+ print-color-adjust: exact;
2170
+ color: ${cover.textColor || "#0F172A"};
2171
+ }
2172
+ .markforge-cover.cover-modern {
2173
+ border-top: 8px solid #0D998D;
2174
+ }
2175
+ .markforge-cover.cover-corporate-split {
2176
+ border-left: 12px solid #0D998D;
2177
+ }
2178
+ .markforge-cover.cover-card {
2179
+ background: #F8FAFC;
2180
+ }
2181
+ .cover-top {
2182
+ display: flex;
2183
+ justify-content: space-between;
2184
+ align-items: flex-start;
2185
+ width: 100%;
2186
+ }
2187
+ .cover-badge {
2188
+ display: inline-block;
2189
+ padding: 0.35rem 0.85rem;
2190
+ font-size: 0.78rem;
2191
+ font-weight: 700;
2192
+ letter-spacing: 0.08em;
2193
+ text-transform: uppercase;
2194
+ background-color: #ECFDFD;
2195
+ color: #0D998D;
2196
+ border-radius: 4px;
2197
+ border: 1px solid #33CDCF;
2198
+ }
2199
+ .cover-body {
2200
+ margin: auto 0;
2201
+ }
2202
+ .cover-title {
2203
+ font-size: 2.8rem;
2204
+ font-weight: 800;
2205
+ line-height: 1.15;
2206
+ margin: 0 0 1rem 0;
2207
+ color: inherit;
2208
+ }
2209
+ .cover-subtitle {
2210
+ font-size: 1.35rem;
2211
+ font-weight: 400;
2212
+ color: #64748B;
2213
+ margin: 0 0 2rem 0;
2214
+ line-height: 1.4;
2215
+ }
2216
+ .cover-meta {
2217
+ display: flex;
2218
+ flex-direction: column;
2219
+ gap: 0.5rem;
2220
+ border-top: 1.5px solid #E2E8F0;
2221
+ padding-top: 1.5rem;
2222
+ max-width: 480px;
2223
+ }
2224
+ .cover-meta-item {
2225
+ font-size: 0.92rem;
2226
+ display: flex;
2227
+ gap: 0.75rem;
2228
+ }
2229
+ .cover-meta-label {
2230
+ font-weight: 600;
2231
+ color: #64748B;
2232
+ min-width: 110px;
2233
+ }
2234
+ .cover-meta-value {
2235
+ font-weight: 500;
2236
+ color: #0F172A;
2237
+ }
2238
+ .cover-bottom {
2239
+ display: flex;
2240
+ justify-content: space-between;
2241
+ align-items: flex-end;
2242
+ font-size: 0.82rem;
2243
+ color: #94A3B8;
1715
2244
  }
1716
2245
  @media print {
1717
- .document-watermark {
1718
- position: fixed;
1719
- top: 0;
1720
- left: 0;
1721
- width: 100vw;
2246
+ .markforge-cover {
2247
+ page-break-after: always;
2248
+ break-after: page;
1722
2249
  height: 100vh;
2250
+ min-height: 100vh;
2251
+ max-height: 100vh;
2252
+ box-sizing: border-box;
2253
+ overflow: hidden;
2254
+ margin: 0;
1723
2255
  -webkit-print-color-adjust: exact;
1724
2256
  print-color-adjust: exact;
1725
2257
  }
1726
2258
  }
1727
2259
  `;
1728
- watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
1729
- <script>
1730
- (function() {
1731
- try {
1732
- var canvas = document.createElement('canvas');
1733
- var dpr = 3;
1734
- var width = 800;
1735
- var height = 1100;
1736
- canvas.width = Math.round(width * dpr);
1737
- canvas.height = Math.round(height * dpr);
1738
- var ctx = canvas.getContext('2d');
1739
- if (ctx) {
1740
- ctx.scale(dpr, dpr);
1741
- ctx.translate(width / 2, height / 2);
1742
- ctx.rotate((${wm.rotate} * Math.PI) / 180);
1743
- ctx.textAlign = 'center';
1744
- ctx.textBaseline = 'middle';
1745
- ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
1746
- ctx.fillStyle = '${wm.color}';
1747
- ctx.globalAlpha = ${wm.opacity};
1748
- try { ctx.letterSpacing = '0.15em'; } catch(e) {}
1749
- ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
1750
- var dataUrl = canvas.toDataURL('image/png');
1751
- var wmEl = document.getElementById('markforge-watermark');
1752
- if (wmEl) {
1753
- wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
1754
- wmEl.style.backgroundRepeat = 'no-repeat';
1755
- wmEl.style.backgroundPosition = 'center center';
1756
- wmEl.style.backgroundSize = 'contain';
1757
- }
1758
- }
1759
- } catch(err) {}
2260
+ const html = ` <section class="markforge-cover cover-${cover.preset}">
2261
+ <div class="cover-top">
2262
+ ${logoHtml}
2263
+ ${badgeHtml}
2264
+ </div>
2265
+ <div class="cover-body">
2266
+ ${titleHtml}
2267
+ ${subtitleHtml}
2268
+ ${metaHtml}
2269
+ </div>
2270
+ <div class="cover-bottom">
2271
+ ${footerHtml}
2272
+ </div>
2273
+ </section>
2274
+ `;
2275
+ return { html, css };
2276
+ }
2277
+ async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
2278
+ let logoHtml = "";
2279
+ if (backCover.logo) {
2280
+ const resolved = await resolveImage(backCover.logo, baseDir);
2281
+ const src = resolved ? resolved.dataUri : backCover.logo;
2282
+ const widthStyle = backCover.logoWidth ? `style="width: ${typeof backCover.logoWidth === "number" ? `${backCover.logoWidth}px` : backCover.logoWidth}; max-width: 100%;"` : `style="max-width: 160px; height: auto;"`;
2283
+ logoHtml = `<div class="back-logo"><img src="${src}" alt="Brand Logo" ${widthStyle} /></div>`;
2284
+ }
2285
+ const badgeHtml = backCover.badge ? `<div class="back-badge" style="${backCover.badgeColor ? `background-color: ${backCover.badgeColor};` : ""}${backCover.badgeTextColor ? `color: ${backCover.badgeTextColor};` : ""}">${escapeHtml(backCover.badge)}</div>` : "";
2286
+ const titleHtml = `<h1 class="back-title">${escapeHtml(backCover.title)}</h1>`;
2287
+ const subtitleHtml = backCover.subtitle ? `<div class="back-subtitle">${escapeHtml(backCover.subtitle)}</div>` : "";
2288
+ const contactItems = [];
2289
+ if (backCover.company) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Organization:</span> <span class="back-contact-value">${escapeHtml(backCover.company)}</span></div>`);
2290
+ if (backCover.address) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Address:</span> <span class="back-contact-value">${escapeHtml(backCover.address)}</span></div>`);
2291
+ if (backCover.email) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Email:</span> <a href="mailto:${escapeHtml(backCover.email)}" class="back-contact-link">${escapeHtml(backCover.email)}</a></div>`);
2292
+ if (backCover.phone) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Phone:</span> <span class="back-contact-value">${escapeHtml(backCover.phone)}</span></div>`);
2293
+ if (backCover.website) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Website:</span> <a href="${escapeHtml(backCover.website)}" target="_blank" class="back-contact-link">${escapeHtml(backCover.website)}</a></div>`);
2294
+ if (backCover.social) {
2295
+ for (const [network, url] of Object.entries(backCover.social)) {
2296
+ if (url) {
2297
+ contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">${escapeHtml(network.toUpperCase())}:</span> <a href="${escapeHtml(url)}" target="_blank" class="back-contact-link">${escapeHtml(url)}</a></div>`);
2298
+ }
2299
+ }
2300
+ }
2301
+ const contactHtml = contactItems.length > 0 ? `<div class="back-contact-grid">${contactItems.join("\n")}</div>` : "";
2302
+ const copyrightHtml = backCover.copyright ? `<div class="back-copyright">${escapeHtml(backCover.copyright)}</div>` : "";
2303
+ const isDark = backCover.preset === "corporate";
2304
+ const css = `
2305
+ .markforge-back-cover {
2306
+ min-height: 100vh;
2307
+ box-sizing: border-box;
2308
+ display: flex;
2309
+ flex-direction: column;
2310
+ justify-content: space-between;
2311
+ padding: 4rem 3.5rem;
2312
+ page-break-before: always;
2313
+ break-before: page;
2314
+ position: relative;
2315
+ z-index: 2;
2316
+ background: ${backCover.bgGradient || (isDark ? "#0F172A" : "#FFFFFF")};
2317
+ color: ${backCover.textColor || (isDark ? "#F8FAFC" : "#0F172A")};
2318
+ }
2319
+ .markforge-back-cover.back-modern {
2320
+ border-bottom: 8px solid #0D998D;
2321
+ }
2322
+ .markforge-back-cover.back-corporate {
2323
+ border-left: 12px solid #33CDCF;
2324
+ }
2325
+ .markforge-back-cover.back-card {
2326
+ background: #F8FAFC;
2327
+ }
2328
+ .back-top {
2329
+ display: flex;
2330
+ justify-content: space-between;
2331
+ align-items: flex-start;
2332
+ width: 100%;
2333
+ }
2334
+ .back-badge {
2335
+ display: inline-block;
2336
+ padding: 0.35rem 0.85rem;
2337
+ font-size: 0.78rem;
2338
+ font-weight: 700;
2339
+ letter-spacing: 0.08em;
2340
+ text-transform: uppercase;
2341
+ background-color: #ECFDFD;
2342
+ color: #0D998D;
2343
+ border-radius: 4px;
2344
+ border: 1px solid #33CDCF;
2345
+ }
2346
+ .back-body {
2347
+ margin: auto 0;
2348
+ }
2349
+ .back-title {
2350
+ font-size: 2.6rem;
2351
+ font-weight: 800;
2352
+ line-height: 1.15;
2353
+ margin: 0 0 0.75rem 0;
2354
+ color: inherit;
2355
+ }
2356
+ .back-subtitle {
2357
+ font-size: 1.25rem;
2358
+ font-weight: 400;
2359
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2360
+ margin: 0 0 2rem 0;
2361
+ line-height: 1.4;
2362
+ }
2363
+ .back-contact-grid {
2364
+ display: flex;
2365
+ flex-direction: column;
2366
+ gap: 0.6rem;
2367
+ border-top: 1.5px solid ${isDark ? "#334155" : "#E2E8F0"};
2368
+ padding-top: 1.5rem;
2369
+ max-width: 540px;
2370
+ }
2371
+ .back-contact-item {
2372
+ font-size: 0.92rem;
2373
+ display: flex;
2374
+ gap: 0.75rem;
2375
+ }
2376
+ .back-contact-label {
2377
+ font-weight: 600;
2378
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2379
+ min-width: 110px;
2380
+ }
2381
+ .back-contact-link {
2382
+ color: #0D998D;
2383
+ text-decoration: none;
2384
+ font-weight: 600;
2385
+ }
2386
+ .back-contact-link:hover {
2387
+ text-decoration: underline;
2388
+ }
2389
+ .back-copyright {
2390
+ font-size: 0.82rem;
2391
+ color: ${isDark ? "#64748B" : "#94A3B8"};
2392
+ border-top: 1px solid ${isDark ? "#1E293B" : "#F1F5F9"};
2393
+ padding-top: 1rem;
2394
+ margin-top: 2rem;
2395
+ }
2396
+ @media print {
2397
+ .markforge-back-cover {
2398
+ page: back-cover-page;
2399
+ page-break-before: always;
2400
+ break-before: page;
2401
+ page-break-after: avoid;
2402
+ break-after: avoid;
2403
+ min-height: 100vh;
2404
+ height: 100vh;
2405
+ max-height: 100vh;
2406
+ margin: 0;
2407
+ box-sizing: border-box;
2408
+ overflow: hidden;
2409
+ -webkit-print-color-adjust: exact;
2410
+ print-color-adjust: exact;
2411
+ }
2412
+ }
2413
+ `;
2414
+ const html = ` <section class="markforge-back-cover back-${backCover.preset}">
2415
+ <div class="back-top">
2416
+ ${logoHtml}
2417
+ ${badgeHtml}
2418
+ </div>
2419
+ <div class="back-body">
2420
+ ${titleHtml}
2421
+ ${subtitleHtml}
2422
+ ${contactHtml}
2423
+ </div>
2424
+ ${copyrightHtml}
2425
+ </section>
2426
+ `;
2427
+ return { html, css };
2428
+ }
2429
+ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2430
+ var _a, _b;
2431
+ const resolved = resolveDocumentConfig(doc.metadata, config);
2432
+ const baseThemeCss = generateThemeCss(resolved.theme);
2433
+ let customCss = "";
2434
+ for (const cssPath of resolved.css) {
2435
+ const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
2436
+ if (fs3.existsSync(fullCssPath)) {
2437
+ customCss += `
2438
+ /* Custom CSS: ${cssPath} */
2439
+ ` + fs3.readFileSync(fullCssPath, "utf-8");
2440
+ }
2441
+ }
2442
+ const inlinedCss = doc.inlinedStyles.join("\n");
2443
+ const extraCss = `
2444
+ .markforge-columns {
2445
+ display: grid;
2446
+ grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
2447
+ gap: var(--col-gap, 1.5rem);
2448
+ margin: 1.5rem 0;
2449
+ }
2450
+ .markforge-col {
2451
+ min-width: 0;
2452
+ }
2453
+ .markforge-fnref {
2454
+ text-decoration: none;
2455
+ font-size: 0.8em;
2456
+ vertical-align: super;
2457
+ color: #0D998D;
2458
+ font-weight: 700;
2459
+ }
2460
+ .markforge-footnotes {
2461
+ margin-top: 3rem;
2462
+ padding-top: 1rem;
2463
+ font-size: 0.88rem;
2464
+ color: #64748B;
2465
+ }
2466
+ .markforge-footnotes hr {
2467
+ border: 0;
2468
+ border-top: 1px solid #E2E8F0;
2469
+ margin-bottom: 1rem;
2470
+ }
2471
+ .markforge-fn-return {
2472
+ text-decoration: none;
2473
+ color: #0D998D;
2474
+ }
2475
+ .math-block {
2476
+ margin: 1.5rem 0;
2477
+ text-align: center;
2478
+ overflow-x: auto;
2479
+ }
2480
+ `;
2481
+ let coverHtml = "";
2482
+ let coverCss = "";
2483
+ if (resolved.coverPage && resolved.coverPage.enabled) {
2484
+ const coverRes = await renderCoverPageHtml(resolved.coverPage, baseDir);
2485
+ coverHtml = coverRes.html;
2486
+ coverCss = coverRes.css;
2487
+ }
2488
+ let backHtml = "";
2489
+ let backCss = "";
2490
+ if (resolved.backCover && resolved.backCover.enabled) {
2491
+ const backRes = await renderBackCoverHtml(resolved.backCover, baseDir);
2492
+ backHtml = backRes.html;
2493
+ backCss = backRes.css;
2494
+ }
2495
+ let bodyHtml = "";
2496
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2497
+ bodyHtml += ` <header class="document-header">
2498
+ `;
2499
+ bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
2500
+ `;
2501
+ if (resolved.subtitle) {
2502
+ bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
2503
+ `;
2504
+ }
2505
+ if (resolved.author || resolved.date || resolved.version) {
2506
+ bodyHtml += ` <div class="document-meta">
2507
+ `;
2508
+ if (resolved.author) {
2509
+ bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
2510
+ `;
2511
+ }
2512
+ if (resolved.version) {
2513
+ bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
2514
+ `;
2515
+ }
2516
+ if (resolved.date) {
2517
+ bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
2518
+ `;
2519
+ }
2520
+ bodyHtml += ` </div>
2521
+ `;
2522
+ }
2523
+ bodyHtml += ` </header>
2524
+ `;
2525
+ }
2526
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
2527
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
2528
+ }
2529
+ if (resolved.toc && doc.tocEntries.length > 0) {
2530
+ bodyHtml += ` <nav class="table-of-contents">
2531
+ `;
2532
+ bodyHtml += ` <h2>Table of Contents</h2>
2533
+ <ul>
2534
+ `;
2535
+ for (const entry of doc.tocEntries) {
2536
+ const indent = " ".repeat(entry.level);
2537
+ bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
2538
+ `;
2539
+ }
2540
+ bodyHtml += ` </ul>
2541
+ </nav>
2542
+ `;
2543
+ }
2544
+ const mergedTokens = {
2545
+ ...config.metadata,
2546
+ ...doc.metadata,
2547
+ ...resolved,
2548
+ title: resolved.title,
2549
+ subtitle: resolved.subtitle,
2550
+ author: resolved.author,
2551
+ version: resolved.version,
2552
+ date: resolved.date,
2553
+ company: resolved.company
2554
+ };
2555
+ const nodesHtml = await renderNodesToHtml(doc.nodes, resolved, baseDir, mergedTokens);
2556
+ bodyHtml += ` <main class="markforge-content-body">
2557
+ ${nodesHtml} </main>
2558
+ `;
2559
+ let footnotesHtml = "";
2560
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
2561
+ let fnListHtml = "";
2562
+ for (const def of doc.footnoteDefs) {
2563
+ const defInner = await renderInlinesToHtml(def.inlines, baseDir, mergedTokens);
2564
+ fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">&#8617;</a></li>
2565
+ `;
2566
+ }
2567
+ footnotesHtml = `
2568
+ <footer class="markforge-footnotes">
2569
+ <hr />
2570
+ <ol>
2571
+ ${fnListHtml} </ol>
2572
+ </footer>
2573
+ `;
2574
+ }
2575
+ let watermarkCss = "";
2576
+ let watermarkHtml = "";
2577
+ if (resolved.watermark) {
2578
+ const wm = resolved.watermark;
2579
+ watermarkCss = `
2580
+ .document-watermark {
2581
+ position: fixed;
2582
+ top: 0;
2583
+ left: 0;
2584
+ right: 0;
2585
+ bottom: 0;
2586
+ width: 100%;
2587
+ height: 100%;
2588
+ pointer-events: none;
2589
+ z-index: 0;
2590
+ user-select: none;
2591
+ -webkit-user-select: none;
2592
+ -webkit-print-color-adjust: exact;
2593
+ print-color-adjust: exact;
2594
+ }
2595
+ .document-container {
2596
+ position: relative;
2597
+ z-index: 1;
2598
+ }
2599
+ @media print {
2600
+ .document-watermark {
2601
+ display: none !important;
2602
+ }
2603
+ }
2604
+ `;
2605
+ watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
2606
+ <script>
2607
+ (function() {
2608
+ try {
2609
+ var canvas = document.createElement('canvas');
2610
+ var dpr = 2;
2611
+ var width = 1200;
2612
+ var height = 1600;
2613
+ canvas.width = width * dpr;
2614
+ canvas.height = height * dpr;
2615
+ var ctx = canvas.getContext('2d');
2616
+ if (ctx) {
2617
+ ctx.scale(dpr, dpr);
2618
+ ctx.translate(width / 2, height / 2);
2619
+ ctx.rotate((-Math.abs(${wm.rotate || 45}) * Math.PI) / 180);
2620
+ ctx.textAlign = 'center';
2621
+ ctx.textBaseline = 'middle';
2622
+ ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
2623
+ ctx.fillStyle = '${wm.color}';
2624
+ ctx.globalAlpha = ${wm.opacity};
2625
+ try { ctx.letterSpacing = '0.15em'; } catch(e) {}
2626
+ ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
2627
+ var dataUrl = canvas.toDataURL('image/png');
2628
+ var wmEl = document.getElementById('markforge-watermark');
2629
+ if (wmEl) {
2630
+ wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
2631
+ wmEl.style.backgroundRepeat = 'no-repeat';
2632
+ wmEl.style.backgroundPosition = 'center center';
2633
+ wmEl.style.backgroundSize = 'contain';
2634
+ }
2635
+ }
2636
+ } catch(err) {}
1760
2637
  })();
1761
2638
  </script>
1762
2639
  `;
@@ -1766,10 +2643,6 @@ ${escapeHtml(node.text || "")}
1766
2643
  if (resolved.signatures && resolved.signatures.items.length > 0) {
1767
2644
  const sig = resolved.signatures;
1768
2645
  const numItems = sig.items.length;
1769
- let justifyCss = "flex-end";
1770
- if (sig.align === "left") justifyCss = "flex-start";
1771
- else if (sig.align === "center") justifyCss = "center";
1772
- else if (sig.align === "space-between") justifyCss = "space-between";
1773
2646
  signaturesCss = `
1774
2647
  .markforge-signatures {
1775
2648
  margin-top: ${sig.spacingBefore};
@@ -1881,6 +2754,10 @@ ${itemCards}
1881
2754
  <style>
1882
2755
  ${THEME_COMPONENTS}
1883
2756
  ${baseThemeCss}
2757
+ ${KATEX_INLINE_CSS}
2758
+ ${extraCss}
2759
+ ${coverCss}
2760
+ ${backCss}
1884
2761
  ${customCss}
1885
2762
  ${inlinedCss}
1886
2763
  ${watermarkCss}
@@ -1888,14 +2765,95 @@ ${signaturesCss}
1888
2765
  </style>
1889
2766
  </head>
1890
2767
  <body>
1891
- ${watermarkHtml} <div class="document-container">
1892
- ${bodyHtml}${signaturesHtml} </div>
2768
+ ${watermarkHtml}${coverHtml} <div class="document-container">
2769
+ ${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
1893
2770
  ${mermaidScript}
1894
- </body>
2771
+ ${backHtml}</body>
1895
2772
  </html>`;
1896
2773
  }
1897
2774
 
1898
2775
  // src/core/pdf/pdfBuilder.ts
2776
+ function escapeXml(str) {
2777
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2778
+ }
2779
+ function generateWatermarkPngBuffer(chromePath, wm) {
2780
+ const tmpHtml = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
2781
+ const tmpPng = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
2782
+ try {
2783
+ const text = escapeXml(wm.text.toUpperCase());
2784
+ const fontSize = (wm.fontSize || 52) * 1.5;
2785
+ const color = wm.color || "#E11D48";
2786
+ const opacity = wm.opacity !== void 0 ? wm.opacity : 0.12;
2787
+ const rotate = wm.rotate !== void 0 ? wm.rotate : -45;
2788
+ const html = `<!DOCTYPE html>
2789
+ <html>
2790
+ <head>
2791
+ <meta charset="utf-8">
2792
+ <style>
2793
+ html, body {
2794
+ margin: 0;
2795
+ padding: 0;
2796
+ width: 1200px;
2797
+ height: 1600px;
2798
+ background: transparent;
2799
+ overflow: hidden;
2800
+ }
2801
+ .wm-box {
2802
+ width: 1200px;
2803
+ height: 1600px;
2804
+ display: flex;
2805
+ align-items: center;
2806
+ justify-content: center;
2807
+ transform: rotate(${rotate}deg);
2808
+ }
2809
+ .wm-text {
2810
+ font-family: system-ui, -apple-system, sans-serif;
2811
+ font-weight: 900;
2812
+ font-size: ${fontSize}px;
2813
+ color: ${color};
2814
+ opacity: ${opacity};
2815
+ letter-spacing: 0.15em;
2816
+ text-transform: uppercase;
2817
+ white-space: nowrap;
2818
+ }
2819
+ </style>
2820
+ </head>
2821
+ <body>
2822
+ <div class="wm-box"><span class="wm-text">${text}</span></div>
2823
+ </body>
2824
+ </html>`;
2825
+ fs4.writeFileSync(tmpHtml, html, "utf8");
2826
+ const fileUrl = pathToFileURL2(tmpHtml).href;
2827
+ const isWin = process.platform === "win32";
2828
+ spawnSync(
2829
+ chromePath,
2830
+ [
2831
+ "--headless=new",
2832
+ "--disable-gpu",
2833
+ "--disable-sync",
2834
+ "--disable-extensions",
2835
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2836
+ `--screenshot=${tmpPng}`,
2837
+ "--window-size=1200,1600",
2838
+ "--default-background-color=00000000",
2839
+ fileUrl
2840
+ ],
2841
+ { timeout: 15e3, windowsHide: true }
2842
+ );
2843
+ if (fs4.existsSync(tmpPng) && fs4.statSync(tmpPng).size > 0) {
2844
+ return fs4.readFileSync(tmpPng);
2845
+ }
2846
+ return null;
2847
+ } catch {
2848
+ return null;
2849
+ } finally {
2850
+ try {
2851
+ if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
2852
+ if (fs4.existsSync(tmpPng)) fs4.unlinkSync(tmpPng);
2853
+ } catch {
2854
+ }
2855
+ }
2856
+ }
1899
2857
  function findChromeExecutable() {
1900
2858
  if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1901
2859
  return process.env.CHROME_PATH;
@@ -1960,7 +2918,7 @@ function findChromeExecutable() {
1960
2918
  return null;
1961
2919
  }
1962
2920
  function injectPagedMediaStyles(html, config, metadata) {
1963
- var _a, _b, _c, _d, _e, _f;
2921
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1964
2922
  const resolved = resolveDocumentConfig(metadata || {}, config);
1965
2923
  const size = resolved.paperSize;
1966
2924
  const orientation = resolved.orientation;
@@ -2004,6 +2962,66 @@ function injectPagedMediaStyles(html, config, metadata) {
2004
2962
  ${fontStyle}
2005
2963
  }`;
2006
2964
  };
2965
+ const coverPageCss = ((_a = resolved.coverPage) == null ? void 0 : _a.enabled) ? `
2966
+ @page :first {
2967
+ margin-top: 0;
2968
+ margin-bottom: 0;
2969
+ margin-left: 0;
2970
+ margin-right: 0;
2971
+ background-image: none !important;
2972
+ @top-left { content: none; }
2973
+ @top-center { content: none; }
2974
+ @top-right { content: none; }
2975
+ @bottom-left { content: none; }
2976
+ @bottom-center { content: none; }
2977
+ @bottom-right { content: none; }
2978
+ }` : "";
2979
+ const backCoverCss = ((_b = resolved.backCover) == null ? void 0 : _b.enabled) ? `
2980
+ @page back-cover-page {
2981
+ size: ${size} ${orientation};
2982
+ margin: 0;
2983
+ background-image: none !important;
2984
+ @top-left { content: none; }
2985
+ @top-center { content: none; }
2986
+ @top-right { content: none; }
2987
+ @bottom-left { content: none; }
2988
+ @bottom-center { content: none; }
2989
+ @bottom-right { content: none; }
2990
+ }
2991
+ .markforge-back-cover {
2992
+ page: back-cover-page;
2993
+ min-height: 100vh;
2994
+ height: 100vh;
2995
+ box-sizing: border-box;
2996
+ break-before: page;
2997
+ break-after: avoid;
2998
+ }` : "";
2999
+ const tocPageCss = resolved.toc ? `
3000
+ @page toc-page {
3001
+ size: ${size} ${orientation};
3002
+ margin-top: ${top};
3003
+ margin-bottom: ${bottom};
3004
+ margin-left: ${left};
3005
+ margin-right: ${right};
3006
+ ${buildZoneCss("top-left", (_c = resolved.header) == null ? void 0 : _c.left)}
3007
+ ${buildZoneCss("top-center", (_d = resolved.header) == null ? void 0 : _d.center)}
3008
+ ${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
3009
+ ${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
3010
+ ${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
3011
+ @bottom-right {
3012
+ content: counter(page, lower-roman);
3013
+ font-size: 9pt;
3014
+ color: #94a3b8;
3015
+ }
3016
+ }
3017
+ .table-of-contents {
3018
+ page: toc-page;
3019
+ page-break-after: always;
3020
+ break-after: page;
3021
+ }
3022
+ .markforge-content-body {
3023
+ counter-reset: page 1;
3024
+ }` : "";
2007
3025
  const pagedCss = `
2008
3026
  @page {
2009
3027
  size: ${size} ${orientation};
@@ -2011,15 +3029,19 @@ function injectPagedMediaStyles(html, config, metadata) {
2011
3029
  margin-bottom: ${bottom};
2012
3030
  margin-left: ${left};
2013
3031
  margin-right: ${right};
2014
- ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
2015
- ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
2016
- ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
2017
- ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
2018
- ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
2019
- ${buildZoneCss("bottom-right", (_f = resolved.footer) == null ? void 0 : _f.right, true)}
3032
+ ${buildZoneCss("top-left", (_h = resolved.header) == null ? void 0 : _h.left)}
3033
+ ${buildZoneCss("top-center", (_i = resolved.header) == null ? void 0 : _i.center)}
3034
+ ${buildZoneCss("top-right", (_j = resolved.header) == null ? void 0 : _j.right)}
3035
+ ${buildZoneCss("bottom-left", (_k = resolved.footer) == null ? void 0 : _k.left)}
3036
+ ${buildZoneCss("bottom-center", (_l = resolved.footer) == null ? void 0 : _l.center)}
3037
+ ${buildZoneCss("bottom-right", (_m = resolved.footer) == null ? void 0 : _m.right, true)}
2020
3038
  }
3039
+ ${coverPageCss}
3040
+ ${tocPageCss}
3041
+ ${backCoverCss}
2021
3042
  @media print {
2022
3043
  body { padding: 0; }
3044
+ .document-watermark { display: none !important; }
2023
3045
  h1, h2, h3, pre, table, blockquote, .callout {
2024
3046
  break-inside: avoid;
2025
3047
  }
@@ -2067,6 +3089,7 @@ startxref
2067
3089
  return Buffer.from(pdfBody, "utf-8");
2068
3090
  }
2069
3091
  async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
3092
+ var _a, _b, _c;
2070
3093
  const baseHtml = await buildHtmlDocument(doc, config, baseDir);
2071
3094
  const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
2072
3095
  const chromePath = findChromeExecutable();
@@ -2076,6 +3099,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2076
3099
  const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
2077
3100
  const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
2078
3101
  const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
3102
+ const isWin = process.platform === "win32";
2079
3103
  const isolatedFlags = [
2080
3104
  `--user-data-dir=${tmpProfile}`,
2081
3105
  "--no-first-run",
@@ -2086,7 +3110,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2086
3110
  "--disable-default-apps",
2087
3111
  "--disable-extensions",
2088
3112
  "--disable-domain-reliability",
2089
- "--disable-client-side-phishing-detection",
2090
3113
  "--disable-breakpad",
2091
3114
  "--disable-component-extensions-with-background-pages",
2092
3115
  "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
@@ -2095,12 +3118,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2095
3118
  "--mute-audio",
2096
3119
  "--no-service-autorun",
2097
3120
  "--disable-gpu",
2098
- "--no-sandbox",
2099
- "--disable-setuid-sandbox",
2100
- "--allow-file-access-from-files",
2101
- "--disable-web-security",
3121
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2102
3122
  "--force-color-profile=srgb",
2103
- "--no-pdf-header-footer"
3123
+ "--no-pdf-header-footer",
3124
+ "--window-size=1200,1600"
2104
3125
  ];
2105
3126
  try {
2106
3127
  fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
@@ -2115,7 +3136,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2115
3136
  `--print-to-pdf=${tmpPdf}`,
2116
3137
  fileUrl
2117
3138
  ],
2118
- { timeout: 3e4 }
3139
+ { timeout: 3e4, windowsHide: true }
2119
3140
  );
2120
3141
  if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
2121
3142
  res = spawnSync(
@@ -2126,12 +3147,79 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2126
3147
  `--print-to-pdf=${tmpPdf}`,
2127
3148
  fileUrl
2128
3149
  ],
2129
- { timeout: 3e4 }
3150
+ { timeout: 3e4, windowsHide: true }
2130
3151
  );
2131
3152
  }
2132
3153
  if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
2133
3154
  const pdfBuffer = fs4.readFileSync(tmpPdf);
2134
- return pdfBuffer;
3155
+ try {
3156
+ const pdfDoc = await PDFDocument.load(pdfBuffer);
3157
+ const resolved = resolveDocumentConfig(doc.metadata, config);
3158
+ if (resolved.title) pdfDoc.setTitle(resolved.title);
3159
+ if (resolved.author) pdfDoc.setAuthor(resolved.author);
3160
+ if (resolved.subtitle) pdfDoc.setSubject(resolved.subtitle);
3161
+ pdfDoc.setCreator("MarkForge Enterprise Document Generator");
3162
+ pdfDoc.setProducer("MarkForge (by Ma'sum)");
3163
+ pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
3164
+ if (((_a = resolved.backCover) == null ? void 0 : _a.enabled) && pdfDoc.getPageCount() > 2) {
3165
+ pdfDoc.removePage(pdfDoc.getPageCount() - 1);
3166
+ }
3167
+ if (resolved.watermark) {
3168
+ const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
3169
+ if (wmPng) {
3170
+ const embeddedPng = await pdfDoc.embedPng(wmPng);
3171
+ const totalPages = pdfDoc.getPageCount();
3172
+ const pages = pdfDoc.getPages();
3173
+ const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
3174
+ const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? totalPages - 1 : totalPages;
3175
+ for (let i = startPageIndex; i < endPageIndex; i++) {
3176
+ const page = pages[i];
3177
+ const { width, height } = page.getSize();
3178
+ page.drawImage(embeddedPng, {
3179
+ x: 0,
3180
+ y: 0,
3181
+ width,
3182
+ height
3183
+ });
3184
+ }
3185
+ }
3186
+ }
3187
+ const savedBytes = await pdfDoc.save();
3188
+ let finalBuffer = Buffer.from(savedBytes);
3189
+ if (resolved.security) {
3190
+ const sec = resolved.security;
3191
+ const hasUserPassword = typeof sec.userPassword === "string" && sec.userPassword.length > 0;
3192
+ const hasOwnerPassword = typeof sec.ownerPassword === "string" && sec.ownerPassword.length > 0;
3193
+ if (hasUserPassword || hasOwnerPassword) {
3194
+ try {
3195
+ const userPass = sec.userPassword ?? "";
3196
+ const ownerPass = sec.ownerPassword ?? userPass;
3197
+ const perms = sec.permissions;
3198
+ const encryptedBytes = await encryptPDF(
3199
+ new Uint8Array(finalBuffer),
3200
+ userPass,
3201
+ {
3202
+ ownerPassword: ownerPass,
3203
+ algorithm: "AES-256",
3204
+ allowPrinting: (perms == null ? void 0 : perms.printing) !== "none",
3205
+ allowHighQualityPrint: (perms == null ? void 0 : perms.printing) === "highResolution",
3206
+ allowModifying: (perms == null ? void 0 : perms.modifying) ?? true,
3207
+ allowCopying: (perms == null ? void 0 : perms.copying) ?? true,
3208
+ allowAnnotating: (perms == null ? void 0 : perms.annotating) ?? true,
3209
+ allowFillingForms: (perms == null ? void 0 : perms.fillingForms) ?? true,
3210
+ allowExtraction: (perms == null ? void 0 : perms.contentAccessibility) ?? true,
3211
+ allowAssembly: (perms == null ? void 0 : perms.documentAssembly) ?? true
3212
+ }
3213
+ );
3214
+ finalBuffer = Buffer.from(encryptedBytes);
3215
+ } catch {
3216
+ }
3217
+ }
3218
+ }
3219
+ return finalBuffer;
3220
+ } catch {
3221
+ return pdfBuffer;
3222
+ }
2135
3223
  }
2136
3224
  } catch {
2137
3225
  } finally {
@@ -2386,6 +3474,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2386
3474
  );
2387
3475
  continue;
2388
3476
  }
3477
+ if (span.type === "mathInline") {
3478
+ runs.push(
3479
+ new TextRun({
3480
+ text: span.content,
3481
+ font: "Cambria Math",
3482
+ italics: true,
3483
+ size: options.size,
3484
+ color: options.color || "0F172A"
3485
+ })
3486
+ );
3487
+ continue;
3488
+ }
3489
+ if (span.type === "footnoteRef") {
3490
+ runs.push(
3491
+ new TextRun({
3492
+ text: `[${span.content}]`,
3493
+ superScript: true,
3494
+ color: "009DA0",
3495
+ bold: true
3496
+ })
3497
+ );
3498
+ continue;
3499
+ }
2389
3500
  runs.push(
2390
3501
  new TextRun({
2391
3502
  text: span.content,
@@ -2400,7 +3511,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2400
3511
  return runs;
2401
3512
  }
2402
3513
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2403
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3514
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2404
3515
  const resolved = resolveDocumentConfig(doc.metadata, config);
2405
3516
  const docElements = [];
2406
3517
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -2411,7 +3522,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2411
3522
  const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2412
3523
  const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2413
3524
  const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2414
- if (resolved.title) {
3525
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2415
3526
  docElements.push(
2416
3527
  new Paragraph({
2417
3528
  children: [
@@ -2472,6 +3583,9 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2472
3583
  );
2473
3584
  }
2474
3585
  }
3586
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
3587
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
3588
+ }
2475
3589
  if (resolved.toc) {
2476
3590
  const headingNodes = doc.nodes.filter(
2477
3591
  (n) => n.type === "heading" && typeof n.level === "number" && n.level >= 1 && n.level <= 3
@@ -2541,7 +3655,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2541
3655
  ]
2542
3656
  });
2543
3657
  docElements.push(tocCard);
2544
- docElements.push(new Paragraph({ spacing: { after: 200 } }));
3658
+ docElements.push(
3659
+ new Paragraph({
3660
+ children: [new PageBreak()]
3661
+ })
3662
+ );
2545
3663
  }
2546
3664
  }
2547
3665
  for (const node of doc.nodes) {
@@ -2551,7 +3669,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2551
3669
  font: defaultFont,
2552
3670
  size: 34,
2553
3671
  // 17pt
2554
- color: textHex,
3672
+ color: primaryDarkHex,
2555
3673
  bold: true
2556
3674
  });
2557
3675
  docElements.push(
@@ -2597,7 +3715,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2597
3715
  font: defaultFont,
2598
3716
  size: 22,
2599
3717
  // 11pt
2600
- color: textHex,
3718
+ color: primaryDarkHex,
2601
3719
  bold: true
2602
3720
  });
2603
3721
  docElements.push(
@@ -2827,7 +3945,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2827
3945
  }
2828
3946
  if (node.type === "table" && node.children) {
2829
3947
  const tableRows = [];
2830
- const numCols = ((_b = (_a = node.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) || 1;
3948
+ const numCols = ((_d = (_c = node.children[0]) == null ? void 0 : _c.children) == null ? void 0 : _d.length) || 1;
2831
3949
  const colWidth = Math.floor(9e3 / numCols);
2832
3950
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2833
3951
  const rowNode = node.children[rowIdx];
@@ -2837,7 +3955,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2837
3955
  if (rowNode.children) {
2838
3956
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2839
3957
  const cellNode = rowNode.children[colIdx];
2840
- const align = (_c = node.align) == null ? void 0 : _c[colIdx];
3958
+ const align = (_e = node.align) == null ? void 0 : _e[colIdx];
2841
3959
  let alignment = AlignmentType.LEFT;
2842
3960
  if (align === "center") alignment = AlignmentType.CENTER;
2843
3961
  if (align === "right") alignment = AlignmentType.RIGHT;
@@ -2976,16 +4094,123 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2976
4094
  }
2977
4095
  continue;
2978
4096
  }
2979
- }
2980
- if (resolved.signatures && resolved.signatures.items.length > 0) {
2981
- const sig = resolved.signatures;
2982
- const numItems = sig.items.length;
2983
- const contentWidth = Math.max(
2984
- 1e3,
2985
- resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
2986
- );
2987
- docElements.push(new Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
2988
- const sigCells = [];
4097
+ if (node.type === "mathBlock") {
4098
+ docElements.push(
4099
+ new Paragraph({
4100
+ alignment: AlignmentType.CENTER,
4101
+ children: [
4102
+ new TextRun({
4103
+ text: node.text || "",
4104
+ font: "Cambria Math",
4105
+ italics: true,
4106
+ size: 24,
4107
+ // 12pt
4108
+ color: textHex
4109
+ })
4110
+ ],
4111
+ spacing: { before: 180, after: 180 },
4112
+ shading: { fill: cardBgHex, type: ShadingType.CLEAR },
4113
+ border: {
4114
+ top: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
4115
+ bottom: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
4116
+ left: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
4117
+ right: { style: BorderStyle.SINGLE, size: 4, color: borderHex }
4118
+ }
4119
+ })
4120
+ );
4121
+ continue;
4122
+ }
4123
+ if (node.type === "columns") {
4124
+ const cols = node.columnsCount || 2;
4125
+ const contentWidth = Math.max(
4126
+ 1e3,
4127
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4128
+ );
4129
+ const cellWidthDxa = Math.floor(contentWidth / cols);
4130
+ const cells = [];
4131
+ for (const col of node.children || []) {
4132
+ const colParagraphs = [];
4133
+ for (const childNode of col.children || []) {
4134
+ if (childNode.type === "heading") {
4135
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, bold: true, size: 24, color: primaryDarkHex });
4136
+ colParagraphs.push(new Paragraph({ children: runs, spacing: { before: 120, after: 60 } }));
4137
+ } else if (childNode.type === "paragraph") {
4138
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
4139
+ colParagraphs.push(new Paragraph({ children: runs, spacing: { after: 100 } }));
4140
+ } else if (childNode.type === "list" && childNode.children) {
4141
+ for (const item of childNode.children) {
4142
+ const runs = await convertInlinesToTextRuns(item.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
4143
+ colParagraphs.push(new Paragraph({ children: [new TextRun({ text: "\u2022 ", font: defaultFont, color: primaryHex }), ...runs], spacing: { after: 40 } }));
4144
+ }
4145
+ }
4146
+ }
4147
+ if (colParagraphs.length === 0) colParagraphs.push(new Paragraph({}));
4148
+ cells.push(
4149
+ new TableCell({
4150
+ width: { size: cellWidthDxa, type: WidthType.DXA },
4151
+ borders: {
4152
+ top: { style: BorderStyle.NONE, size: 0, color: "auto" },
4153
+ bottom: { style: BorderStyle.NONE, size: 0, color: "auto" },
4154
+ left: { style: BorderStyle.NONE, size: 0, color: "auto" },
4155
+ right: { style: BorderStyle.NONE, size: 0, color: "auto" }
4156
+ },
4157
+ margins: { top: 60, bottom: 60, left: 100, right: 100 },
4158
+ children: colParagraphs
4159
+ })
4160
+ );
4161
+ }
4162
+ docElements.push(
4163
+ new Table({
4164
+ width: { size: 100, type: WidthType.PERCENTAGE },
4165
+ rows: [new TableRow({ children: cells })]
4166
+ })
4167
+ );
4168
+ docElements.push(new Paragraph({ spacing: { after: 120 } }));
4169
+ continue;
4170
+ }
4171
+ }
4172
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
4173
+ docElements.push(
4174
+ new Paragraph({
4175
+ border: {
4176
+ top: { style: BorderStyle.SINGLE, size: 4, color: borderHex, space: 8 }
4177
+ },
4178
+ spacing: { before: 360, after: 120 }
4179
+ })
4180
+ );
4181
+ for (const def of doc.footnoteDefs) {
4182
+ const defRuns = await convertInlinesToTextRuns(def.inlines, baseDir, {
4183
+ font: defaultFont,
4184
+ size: 18,
4185
+ // 9pt
4186
+ color: textMutedHex
4187
+ });
4188
+ docElements.push(
4189
+ new Paragraph({
4190
+ children: [
4191
+ new TextRun({
4192
+ text: `[${def.id}] `,
4193
+ bold: true,
4194
+ color: primaryDarkHex,
4195
+ font: defaultFont,
4196
+ size: 18
4197
+ }),
4198
+ ...defRuns
4199
+ ],
4200
+ spacing: { after: 60 }
4201
+ })
4202
+ );
4203
+ }
4204
+ }
4205
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
4206
+ const sig = resolved.signatures;
4207
+ const numItems = sig.items.length;
4208
+ const contentWidth = Math.max(
4209
+ 1e3,
4210
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4211
+ );
4212
+ docElements.push(new Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
4213
+ const sigCells = [];
2989
4214
  const colWidths = [];
2990
4215
  if (numItems === 1) {
2991
4216
  const cardWidth = Math.min(3400, Math.floor(contentWidth * 0.42));
@@ -3037,7 +4262,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3037
4262
  const centerPos = Math.round(contentWidthTwip / 2);
3038
4263
  const rightPos = contentWidthTwip;
3039
4264
  const headerRuns = [];
3040
- if ((_d = resolved.header) == null ? void 0 : _d.left) {
4265
+ if ((_f = resolved.header) == null ? void 0 : _f.left) {
3041
4266
  headerRuns.push(
3042
4267
  new TextRun({
3043
4268
  text: resolved.header.left.text,
@@ -3050,7 +4275,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3050
4275
  );
3051
4276
  }
3052
4277
  headerRuns.push(new TextRun({ text: " " }));
3053
- if ((_e = resolved.header) == null ? void 0 : _e.center) {
4278
+ if ((_g = resolved.header) == null ? void 0 : _g.center) {
3054
4279
  headerRuns.push(
3055
4280
  new TextRun({
3056
4281
  text: resolved.header.center.text,
@@ -3063,7 +4288,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3063
4288
  );
3064
4289
  }
3065
4290
  headerRuns.push(new TextRun({ text: " " }));
3066
- if ((_f = resolved.header) == null ? void 0 : _f.right) {
4291
+ if ((_h = resolved.header) == null ? void 0 : _h.right) {
3067
4292
  headerRuns.push(
3068
4293
  new TextRun({
3069
4294
  text: resolved.header.right.text,
@@ -3102,7 +4327,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3102
4327
  ]
3103
4328
  }) : void 0;
3104
4329
  const footerRuns = [];
3105
- if ((_g = resolved.footer) == null ? void 0 : _g.left) {
4330
+ if ((_i = resolved.footer) == null ? void 0 : _i.left) {
3106
4331
  footerRuns.push(
3107
4332
  new TextRun({
3108
4333
  text: resolved.footer.left.text,
@@ -3115,7 +4340,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3115
4340
  );
3116
4341
  }
3117
4342
  footerRuns.push(new TextRun({ text: " " }));
3118
- if ((_h = resolved.footer) == null ? void 0 : _h.center) {
4343
+ if ((_j = resolved.footer) == null ? void 0 : _j.center) {
3119
4344
  footerRuns.push(
3120
4345
  new TextRun({
3121
4346
  text: resolved.footer.center.text,
@@ -3128,7 +4353,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3128
4353
  );
3129
4354
  }
3130
4355
  footerRuns.push(new TextRun({ text: " " }));
3131
- if ((_i = resolved.footer) == null ? void 0 : _i.right) {
4356
+ if ((_k = resolved.footer) == null ? void 0 : _k.right) {
3132
4357
  const rZone = resolved.footer.right;
3133
4358
  const rColor = rZone.color.replace("#", "");
3134
4359
  const rSize = (rZone.fontSize || 9) * 2;
@@ -3213,6 +4438,95 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3213
4438
  ]
3214
4439
  }) : void 0;
3215
4440
  const isLandscape = resolved.orientation === "landscape";
4441
+ const docSections = [];
4442
+ if (resolved.coverPage && resolved.coverPage.enabled) {
4443
+ const coverElements = await buildDocxCoverPageElements(
4444
+ resolved.coverPage,
4445
+ defaultFont,
4446
+ textHex,
4447
+ primaryHex,
4448
+ primaryDarkHex,
4449
+ textMutedHex,
4450
+ baseDir
4451
+ );
4452
+ docSections.push({
4453
+ properties: {
4454
+ page: {
4455
+ size: {
4456
+ width: resolved.paperDimensions.widthTwip,
4457
+ height: resolved.paperDimensions.heightTwip,
4458
+ orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
4459
+ },
4460
+ margin: {
4461
+ top: resolved.margins.topTwip,
4462
+ bottom: resolved.margins.bottomTwip,
4463
+ left: resolved.margins.leftTwip,
4464
+ right: resolved.margins.rightTwip
4465
+ }
4466
+ }
4467
+ },
4468
+ headers: void 0,
4469
+ footers: void 0,
4470
+ children: coverElements
4471
+ });
4472
+ }
4473
+ docSections.push({
4474
+ properties: {
4475
+ page: {
4476
+ pageNumbers: {
4477
+ start: 1,
4478
+ formatType: NumberFormat.DECIMAL
4479
+ },
4480
+ size: {
4481
+ width: resolved.paperDimensions.widthTwip,
4482
+ height: resolved.paperDimensions.heightTwip,
4483
+ orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
4484
+ },
4485
+ margin: {
4486
+ top: resolved.margins.topTwip,
4487
+ bottom: resolved.margins.bottomTwip,
4488
+ left: resolved.margins.leftTwip,
4489
+ right: resolved.margins.rightTwip,
4490
+ header: 720,
4491
+ footer: 720
4492
+ }
4493
+ }
4494
+ },
4495
+ headers: docHeader ? { default: docHeader } : void 0,
4496
+ footers: docFooter ? { default: docFooter } : void 0,
4497
+ children: docElements
4498
+ });
4499
+ if (resolved.backCover && resolved.backCover.enabled) {
4500
+ const backElements = await buildDocxBackCoverElements(
4501
+ resolved.backCover,
4502
+ defaultFont,
4503
+ textHex,
4504
+ primaryHex,
4505
+ primaryDarkHex,
4506
+ textMutedHex,
4507
+ baseDir
4508
+ );
4509
+ docSections.push({
4510
+ properties: {
4511
+ page: {
4512
+ size: {
4513
+ width: resolved.paperDimensions.widthTwip,
4514
+ height: resolved.paperDimensions.heightTwip,
4515
+ orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
4516
+ },
4517
+ margin: {
4518
+ top: resolved.margins.topTwip,
4519
+ bottom: resolved.margins.bottomTwip,
4520
+ left: resolved.margins.leftTwip,
4521
+ right: resolved.margins.rightTwip
4522
+ }
4523
+ }
4524
+ },
4525
+ headers: void 0,
4526
+ footers: void 0,
4527
+ children: backElements
4528
+ });
4529
+ }
3216
4530
  const document = new Document({
3217
4531
  styles: {
3218
4532
  default: {
@@ -3233,33 +4547,250 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3233
4547
  }
3234
4548
  }
3235
4549
  },
3236
- sections: [
3237
- {
3238
- properties: {
3239
- page: {
3240
- size: {
3241
- width: resolved.paperDimensions.widthTwip,
3242
- height: resolved.paperDimensions.heightTwip,
3243
- orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
3244
- },
3245
- margin: {
3246
- top: resolved.margins.topTwip,
3247
- bottom: resolved.margins.bottomTwip,
3248
- left: resolved.margins.leftTwip,
3249
- right: resolved.margins.rightTwip,
3250
- header: 720,
3251
- footer: 720
3252
- }
3253
- }
3254
- },
3255
- headers: docHeader ? { default: docHeader } : void 0,
3256
- footers: docFooter ? { default: docFooter } : void 0,
3257
- children: docElements
3258
- }
3259
- ]
4550
+ sections: docSections
3260
4551
  });
3261
4552
  return await Packer.toBuffer(document);
3262
4553
  }
4554
+ async function buildDocxBackCoverElements(backCover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4555
+ var _a;
4556
+ const elements = [];
4557
+ elements.push(new Paragraph({ spacing: { before: 1800 } }));
4558
+ if (backCover.logo) {
4559
+ const resolvedLogo = await resolveImage(backCover.logo, baseDir);
4560
+ if (resolvedLogo) {
4561
+ const logoW = typeof backCover.logoWidth === "number" ? backCover.logoWidth : 140;
4562
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4563
+ elements.push(
4564
+ new Paragraph({
4565
+ children: [
4566
+ new ImageRun({
4567
+ data: resolvedLogo.buffer,
4568
+ transformation: {
4569
+ width: logoW,
4570
+ height: Math.round(logoW * 0.75)
4571
+ },
4572
+ type: logoType
4573
+ })
4574
+ ],
4575
+ spacing: { after: 240 }
4576
+ })
4577
+ );
4578
+ }
4579
+ }
4580
+ if (backCover.badge) {
4581
+ elements.push(
4582
+ new Paragraph({
4583
+ children: [
4584
+ new TextRun({
4585
+ text: `[ ${backCover.badge.toUpperCase()} ]`,
4586
+ font: defaultFont,
4587
+ size: 20,
4588
+ bold: true,
4589
+ color: primaryDarkHex
4590
+ })
4591
+ ],
4592
+ spacing: { after: 240 }
4593
+ })
4594
+ );
4595
+ }
4596
+ elements.push(
4597
+ new Paragraph({
4598
+ children: [
4599
+ new TextRun({
4600
+ text: backCover.title,
4601
+ font: defaultFont,
4602
+ size: 52,
4603
+ // 26pt
4604
+ bold: true,
4605
+ color: textHex
4606
+ })
4607
+ ],
4608
+ spacing: { after: 140 }
4609
+ })
4610
+ );
4611
+ if (backCover.subtitle) {
4612
+ elements.push(
4613
+ new Paragraph({
4614
+ children: [
4615
+ new TextRun({
4616
+ text: backCover.subtitle,
4617
+ font: defaultFont,
4618
+ size: 24,
4619
+ // 12pt
4620
+ color: textMutedHex
4621
+ })
4622
+ ],
4623
+ spacing: { after: 480 }
4624
+ })
4625
+ );
4626
+ }
4627
+ elements.push(
4628
+ new Paragraph({
4629
+ border: {
4630
+ bottom: { style: BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4631
+ },
4632
+ spacing: { after: 480 }
4633
+ })
4634
+ );
4635
+ const contactRuns = [];
4636
+ if (backCover.company) contactRuns.push(new TextRun({ text: `Organization: ${backCover.company}
4637
+ `, font: defaultFont, size: 21, color: textHex }));
4638
+ if (backCover.address) contactRuns.push(new TextRun({ text: `Address: ${backCover.address}
4639
+ `, font: defaultFont, size: 21, color: textHex }));
4640
+ if (backCover.email) contactRuns.push(new TextRun({ text: `Email: ${backCover.email}
4641
+ `, font: defaultFont, size: 21, color: textHex }));
4642
+ if (backCover.phone) contactRuns.push(new TextRun({ text: `Phone: ${backCover.phone}
4643
+ `, font: defaultFont, size: 21, color: textHex }));
4644
+ if (backCover.website) contactRuns.push(new TextRun({ text: `Website: ${backCover.website}
4645
+ `, font: defaultFont, size: 21, color: textHex }));
4646
+ if (backCover.social) {
4647
+ for (const [net, url] of Object.entries(backCover.social)) {
4648
+ if (url) {
4649
+ contactRuns.push(new TextRun({ text: `${net.toUpperCase()}: ${url}
4650
+ `, font: defaultFont, size: 21, color: textHex }));
4651
+ }
4652
+ }
4653
+ }
4654
+ if (contactRuns.length > 0) {
4655
+ elements.push(
4656
+ new Paragraph({
4657
+ children: contactRuns,
4658
+ spacing: { before: 360, after: 360 }
4659
+ })
4660
+ );
4661
+ }
4662
+ if (backCover.copyright) {
4663
+ elements.push(
4664
+ new Paragraph({
4665
+ children: [
4666
+ new TextRun({
4667
+ text: backCover.copyright,
4668
+ font: defaultFont,
4669
+ size: 18,
4670
+ color: "94A3B8"
4671
+ })
4672
+ ],
4673
+ spacing: { before: 720 }
4674
+ })
4675
+ );
4676
+ }
4677
+ return elements;
4678
+ }
4679
+ async function buildDocxCoverPageElements(cover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4680
+ var _a;
4681
+ const elements = [];
4682
+ elements.push(new Paragraph({ spacing: { before: 1800 } }));
4683
+ if (cover.logo) {
4684
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
4685
+ if (resolvedLogo) {
4686
+ const logoW = typeof cover.logoWidth === "number" ? cover.logoWidth : 140;
4687
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4688
+ elements.push(
4689
+ new Paragraph({
4690
+ children: [
4691
+ new ImageRun({
4692
+ data: resolvedLogo.buffer,
4693
+ transformation: {
4694
+ width: logoW,
4695
+ height: Math.round(logoW * 0.75)
4696
+ },
4697
+ type: logoType
4698
+ })
4699
+ ],
4700
+ spacing: { after: 240 }
4701
+ })
4702
+ );
4703
+ }
4704
+ }
4705
+ if (cover.badge) {
4706
+ elements.push(
4707
+ new Paragraph({
4708
+ children: [
4709
+ new TextRun({
4710
+ text: `[ ${cover.badge.toUpperCase()} ]`,
4711
+ font: defaultFont,
4712
+ size: 20,
4713
+ bold: true,
4714
+ color: primaryDarkHex
4715
+ })
4716
+ ],
4717
+ spacing: { after: 240 }
4718
+ })
4719
+ );
4720
+ }
4721
+ elements.push(
4722
+ new Paragraph({
4723
+ children: [
4724
+ new TextRun({
4725
+ text: cover.title,
4726
+ font: defaultFont,
4727
+ size: 56,
4728
+ // 28pt
4729
+ bold: true,
4730
+ color: textHex
4731
+ })
4732
+ ],
4733
+ spacing: { after: 140 }
4734
+ })
4735
+ );
4736
+ if (cover.subtitle) {
4737
+ elements.push(
4738
+ new Paragraph({
4739
+ children: [
4740
+ new TextRun({
4741
+ text: cover.subtitle,
4742
+ font: defaultFont,
4743
+ size: 26,
4744
+ // 13pt
4745
+ color: textMutedHex
4746
+ })
4747
+ ],
4748
+ spacing: { after: 480 }
4749
+ })
4750
+ );
4751
+ }
4752
+ elements.push(
4753
+ new Paragraph({
4754
+ border: {
4755
+ bottom: { style: BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4756
+ },
4757
+ spacing: { after: 480 }
4758
+ })
4759
+ );
4760
+ if (cover.company || cover.author || cover.version || cover.date) {
4761
+ const metaRuns = [];
4762
+ if (cover.company) metaRuns.push(new TextRun({ text: `Organization: ${cover.company}
4763
+ `, font: defaultFont, size: 21, color: textHex }));
4764
+ if (cover.author) metaRuns.push(new TextRun({ text: `Author: ${cover.author}
4765
+ `, font: defaultFont, size: 21, color: textHex }));
4766
+ if (cover.version) metaRuns.push(new TextRun({ text: `Version: ${cover.version}
4767
+ `, font: defaultFont, size: 21, color: textHex }));
4768
+ if (cover.date) metaRuns.push(new TextRun({ text: `Date: ${cover.date}
4769
+ `, font: defaultFont, size: 21, color: textHex }));
4770
+ elements.push(
4771
+ new Paragraph({
4772
+ children: metaRuns,
4773
+ spacing: { before: 360, after: 360 }
4774
+ })
4775
+ );
4776
+ }
4777
+ if (cover.footerText) {
4778
+ elements.push(
4779
+ new Paragraph({
4780
+ children: [
4781
+ new TextRun({
4782
+ text: cover.footerText,
4783
+ font: defaultFont,
4784
+ size: 18,
4785
+ color: "94A3B8"
4786
+ })
4787
+ ],
4788
+ spacing: { before: 720 }
4789
+ })
4790
+ );
4791
+ }
4792
+ return elements;
4793
+ }
3263
4794
  async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
3264
4795
  const cellParagraphs = [];
3265
4796
  if (item.title) {
@@ -3487,14 +5018,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
3487
5018
  };
3488
5019
  }
3489
5020
 
5021
+ // src/server/previewServer.ts
5022
+ import * as http from "http";
5023
+ import * as fs7 from "fs";
5024
+ import * as path7 from "path";
5025
+ async function startPreviewServer(options) {
5026
+ const absoluteFilePath = path7.resolve(process.cwd(), options.filePath);
5027
+ if (!fs7.existsSync(absoluteFilePath)) {
5028
+ throw new Error(`MarkForge preview error: File not found at "${absoluteFilePath}"`);
5029
+ }
5030
+ const baseDir = path7.dirname(absoluteFilePath);
5031
+ const { config: fileConfig } = await loadConfig(void 0, baseDir);
5032
+ const baseConfig = options.config || fileConfig;
5033
+ const port = options.port || 3e3;
5034
+ const sseClients = /* @__PURE__ */ new Set();
5035
+ const broadcastReload = () => {
5036
+ sseClients.forEach((client) => {
5037
+ try {
5038
+ client.write(`event: reload
5039
+ data: ${Date.now()}
5040
+
5041
+ `);
5042
+ } catch {
5043
+ sseClients.delete(client);
5044
+ }
5045
+ });
5046
+ };
5047
+ let debounceTimer = null;
5048
+ const watcher = fs7.watch(baseDir, { recursive: false }, (_event, filename) => {
5049
+ if (!filename) return;
5050
+ const changedPath = path7.resolve(baseDir, filename);
5051
+ if (changedPath === absoluteFilePath || filename.includes("markforge") || filename.endsWith(".css")) {
5052
+ if (debounceTimer) clearTimeout(debounceTimer);
5053
+ debounceTimer = setTimeout(() => {
5054
+ broadcastReload();
5055
+ }, 150);
5056
+ }
5057
+ });
5058
+ const server = http.createServer(async (req, res) => {
5059
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
5060
+ if (url.pathname === "/events") {
5061
+ res.writeHead(200, {
5062
+ "Content-Type": "text/event-stream",
5063
+ "Cache-Control": "no-cache, no-transform",
5064
+ Connection: "keep-alive"
5065
+ });
5066
+ res.write(`data: connected
5067
+
5068
+ `);
5069
+ sseClients.add(res);
5070
+ req.on("close", () => {
5071
+ sseClients.delete(res);
5072
+ });
5073
+ return;
5074
+ }
5075
+ if (url.pathname === "/api/file-content" && req.method === "GET") {
5076
+ try {
5077
+ const content = fs7.readFileSync(absoluteFilePath, "utf-8");
5078
+ res.writeHead(200, { "Content-Type": "application/json" });
5079
+ res.end(
5080
+ JSON.stringify({
5081
+ content,
5082
+ fileName: path7.basename(absoluteFilePath),
5083
+ filePath: absoluteFilePath
5084
+ })
5085
+ );
5086
+ } catch (err) {
5087
+ const msg = err instanceof Error ? err.message : String(err);
5088
+ res.writeHead(500, { "Content-Type": "application/json" });
5089
+ res.end(JSON.stringify({ error: msg }));
5090
+ }
5091
+ return;
5092
+ }
5093
+ if (url.pathname === "/api/save-content" && req.method === "POST") {
5094
+ let body = "";
5095
+ req.on("data", (chunk) => {
5096
+ body += chunk;
5097
+ });
5098
+ req.on("end", () => {
5099
+ try {
5100
+ const parsed = JSON.parse(body);
5101
+ if (typeof parsed.content === "string") {
5102
+ fs7.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
5103
+ broadcastReload();
5104
+ res.writeHead(200, { "Content-Type": "application/json" });
5105
+ res.end(JSON.stringify({ success: true, savedAt: Date.now() }));
5106
+ } else {
5107
+ res.writeHead(400, { "Content-Type": "application/json" });
5108
+ res.end(JSON.stringify({ error: "Missing content field in request body" }));
5109
+ }
5110
+ } catch (err) {
5111
+ const msg = err instanceof Error ? err.message : String(err);
5112
+ res.writeHead(500, { "Content-Type": "application/json" });
5113
+ res.end(JSON.stringify({ error: msg }));
5114
+ }
5115
+ });
5116
+ return;
5117
+ }
5118
+ if (url.pathname === "/api/export" && (req.method === "GET" || req.method === "POST")) {
5119
+ const format = url.searchParams.get("format") || "docx";
5120
+ try {
5121
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5122
+ const doc = parseMarkdownDocument(mdContent);
5123
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5124
+ const mergedConfig = { ...baseConfig, ...resolvedConfig };
5125
+ const fileBase = path7.basename(absoluteFilePath, path7.extname(absoluteFilePath));
5126
+ if (format === "docx") {
5127
+ const buffer = await buildDocxDocument(doc, mergedConfig, baseDir);
5128
+ res.writeHead(200, {
5129
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5130
+ "Content-Disposition": `attachment; filename="${fileBase}.docx"`
5131
+ });
5132
+ res.end(buffer);
5133
+ return;
5134
+ } else if (format === "pdf") {
5135
+ const buffer = await buildPdfDocument(doc, mergedConfig, baseDir);
5136
+ res.writeHead(200, {
5137
+ "Content-Type": "application/pdf",
5138
+ "Content-Disposition": `attachment; filename="${fileBase}.pdf"`
5139
+ });
5140
+ res.end(buffer);
5141
+ return;
5142
+ } else {
5143
+ const html = await buildHtmlDocument(doc, mergedConfig, baseDir);
5144
+ res.writeHead(200, {
5145
+ "Content-Type": "text/html; charset=utf-8",
5146
+ "Content-Disposition": `attachment; filename="${fileBase}.html"`
5147
+ });
5148
+ res.end(html);
5149
+ return;
5150
+ }
5151
+ } catch (err) {
5152
+ const msg = err instanceof Error ? err.message : String(err);
5153
+ res.writeHead(500, { "Content-Type": "text/plain" });
5154
+ res.end(`Export failed: ${msg}`);
5155
+ return;
5156
+ }
5157
+ }
5158
+ if (url.pathname === "/document-content") {
5159
+ try {
5160
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5161
+ const doc = parseMarkdownDocument(mdContent);
5162
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5163
+ const html = await buildHtmlDocument(doc, { ...baseConfig, ...resolvedConfig }, baseDir);
5164
+ const injectedScript = `
5165
+ <script>
5166
+ (function() {
5167
+ var evtSource = new EventSource('/events');
5168
+ evtSource.addEventListener('reload', function() {
5169
+ var scrollPos = window.scrollY;
5170
+ sessionStorage.setItem('markforge_scroll', scrollPos);
5171
+ window.location.reload();
5172
+ });
5173
+ window.addEventListener('load', function() {
5174
+ var saved = sessionStorage.getItem('markforge_scroll');
5175
+ if (saved) {
5176
+ window.scrollTo(0, parseInt(saved, 10));
5177
+ }
5178
+ });
5179
+ })();
5180
+ </script>
5181
+ `;
5182
+ const finalHtml = html.replace("</body>", `${injectedScript}</body>`);
5183
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5184
+ res.end(finalHtml);
5185
+ } catch (err) {
5186
+ const msg = err instanceof Error ? err.message : String(err);
5187
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
5188
+ res.end(`<div style="padding:2rem;font-family:sans-serif;color:#ef4444;background:#fef2f2;border:1px solid #f87171;border-radius:8px;"><h3>MarkForge Compilation Error</h3><pre>${escapeHtml2(msg)}</pre></div>`);
5189
+ }
5190
+ return;
5191
+ }
5192
+ if (url.pathname === "/" || url.pathname === "/index.html") {
5193
+ const fileName = path7.basename(absoluteFilePath);
5194
+ const initialContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5195
+ const appHtml = `<!DOCTYPE html>
5196
+ <html lang="en">
5197
+ <head>
5198
+ <meta charset="UTF-8">
5199
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5200
+ <title>MarkForge Live Studio - ${escapeHtml2(fileName)}</title>
5201
+ <link rel="preconnect" href="https://fonts.googleapis.com">
5202
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5203
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
5204
+ <style>
5205
+ :root {
5206
+ --mf-primary: #0D998D;
5207
+ --mf-primary-dark: #008277;
5208
+ --mf-primary-light: #ECFDFD;
5209
+ --mf-primary-border: #33CDCF;
5210
+ --mf-dark: #0F172A;
5211
+ --mf-slate: #1E293B;
5212
+ --mf-editor-bg: #0F172A;
5213
+ --mf-editor-gutter: #1E293B;
5214
+ --mf-editor-text: #F8FAFC;
5215
+ --mf-muted: #64748B;
5216
+ --mf-light-border: #E2E8F0;
5217
+ --mf-bg: #F1F5F9;
5218
+ }
5219
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5220
+ body {
5221
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5222
+ background: var(--mf-bg);
5223
+ color: var(--mf-dark);
5224
+ display: flex;
5225
+ flex-direction: column;
5226
+ height: 100vh;
5227
+ overflow: hidden;
5228
+ }
5229
+ header {
5230
+ background: #FFFFFF;
5231
+ border-bottom: 1px solid var(--mf-light-border);
5232
+ min-height: 56px;
5233
+ display: flex;
5234
+ flex-wrap: wrap;
5235
+ align-items: center;
5236
+ justify-content: space-between;
5237
+ padding: 0.4rem 1.2rem;
5238
+ z-index: 10;
5239
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
5240
+ gap: 0.75rem;
5241
+ }
5242
+ .brand-section {
5243
+ display: flex;
5244
+ align-items: center;
5245
+ gap: 0.75rem;
5246
+ }
5247
+ .brand-badge {
5248
+ font-size: 0.72rem;
5249
+ font-weight: 800;
5250
+ letter-spacing: 0.08em;
5251
+ background: var(--mf-dark);
5252
+ color: #FFFFFF;
5253
+ padding: 0.25rem 0.55rem;
5254
+ border-radius: 4px;
5255
+ text-transform: uppercase;
5256
+ }
5257
+ .file-name {
5258
+ font-size: 0.9rem;
5259
+ font-weight: 700;
5260
+ color: var(--mf-dark);
5261
+ }
5262
+ .sync-status {
5263
+ display: flex;
5264
+ align-items: center;
5265
+ gap: 0.35rem;
5266
+ font-size: 0.75rem;
5267
+ font-weight: 600;
5268
+ color: var(--mf-primary-dark);
5269
+ background: var(--mf-primary-light);
5270
+ padding: 0.2rem 0.55rem;
5271
+ border-radius: 9999px;
5272
+ border: 1px solid var(--mf-primary-border);
5273
+ }
5274
+ .sync-dot {
5275
+ width: 7px;
5276
+ height: 7px;
5277
+ background-color: var(--mf-primary);
5278
+ border-radius: 50%;
5279
+ box-shadow: 0 0 0 2px rgba(13, 153, 141, 0.2);
5280
+ }
5281
+ .toolbar-section {
5282
+ display: flex;
5283
+ align-items: center;
5284
+ gap: 0.3rem;
5285
+ background: #F8FAFC;
5286
+ padding: 0.25rem 0.4rem;
5287
+ border-radius: 6px;
5288
+ border: 1px solid var(--mf-light-border);
5289
+ }
5290
+ .tool-btn {
5291
+ font-family: 'JetBrains Mono', monospace;
5292
+ font-size: 0.75rem;
5293
+ font-weight: 600;
5294
+ padding: 0.25rem 0.45rem;
5295
+ background: transparent;
5296
+ border: 1px solid transparent;
5297
+ border-radius: 4px;
5298
+ cursor: pointer;
5299
+ color: var(--mf-slate);
5300
+ transition: all 0.1s ease;
5301
+ }
5302
+ .tool-btn:hover {
5303
+ background: #FFFFFF;
5304
+ border-color: var(--mf-light-border);
5305
+ color: var(--mf-primary-dark);
5306
+ }
5307
+ .tool-divider {
5308
+ width: 1px;
5309
+ height: 16px;
5310
+ background: var(--mf-light-border);
5311
+ margin: 0 0.15rem;
5312
+ }
5313
+ .controls {
5314
+ display: flex;
5315
+ align-items: center;
5316
+ gap: 0.5rem;
5317
+ }
5318
+ .view-toggles {
5319
+ display: flex;
5320
+ background: #F1F5F9;
5321
+ padding: 2px;
5322
+ border-radius: 6px;
5323
+ border: 1px solid var(--mf-light-border);
5324
+ }
5325
+ .toggle-btn {
5326
+ font-family: inherit;
5327
+ font-size: 0.74rem;
5328
+ font-weight: 600;
5329
+ padding: 0.25rem 0.55rem;
5330
+ border: none;
5331
+ background: transparent;
5332
+ border-radius: 4px;
5333
+ cursor: pointer;
5334
+ color: var(--mf-muted);
5335
+ transition: all 0.15s ease;
5336
+ }
5337
+ .toggle-btn.active {
5338
+ background: #FFFFFF;
5339
+ color: var(--mf-dark);
5340
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
5341
+ }
5342
+ .btn {
5343
+ font-family: inherit;
5344
+ font-size: 0.78rem;
5345
+ font-weight: 600;
5346
+ padding: 0.35rem 0.75rem;
5347
+ border-radius: 6px;
5348
+ cursor: pointer;
5349
+ text-decoration: none;
5350
+ transition: all 0.15s ease;
5351
+ display: inline-flex;
5352
+ align-items: center;
5353
+ gap: 0.3rem;
5354
+ border: 1px solid var(--mf-light-border);
5355
+ background: #FFFFFF;
5356
+ color: var(--mf-dark);
5357
+ }
5358
+ .btn:hover {
5359
+ background: #F8FAFC;
5360
+ border-color: #CBD5E1;
5361
+ }
5362
+ .btn-primary {
5363
+ background: var(--mf-primary);
5364
+ color: #FFFFFF;
5365
+ border-color: var(--mf-primary);
5366
+ }
5367
+ .btn-primary:hover {
5368
+ background: var(--mf-primary-dark);
5369
+ border-color: var(--mf-primary-dark);
5370
+ }
5371
+ .save-indicator {
5372
+ font-size: 0.75rem;
5373
+ font-weight: 600;
5374
+ color: var(--mf-muted);
5375
+ min-width: 65px;
5376
+ text-align: right;
5377
+ }
5378
+ .save-indicator.saved {
5379
+ color: var(--mf-primary-dark);
5380
+ }
5381
+ .save-indicator.saving {
5382
+ color: #D97706;
5383
+ }
5384
+ .save-indicator.unsaved {
5385
+ color: #E11D48;
5386
+ }
5387
+
5388
+ /* Main Workspace Splitter Layout */
5389
+ main.workspace {
5390
+ flex: 1;
5391
+ display: flex;
5392
+ height: calc(100vh - 56px);
5393
+ overflow: hidden;
5394
+ background: var(--mf-bg);
5395
+ position: relative;
5396
+ }
5397
+ .editor-pane {
5398
+ width: 50%;
5399
+ height: 100%;
5400
+ display: flex;
5401
+ flex-direction: column;
5402
+ background: var(--mf-editor-bg);
5403
+ border-right: 1px solid #334155;
5404
+ overflow: hidden;
5405
+ }
5406
+ .editor-header {
5407
+ background: #090D16;
5408
+ border-bottom: 1px solid #1E293B;
5409
+ padding: 0.4rem 0.8rem;
5410
+ display: flex;
5411
+ align-items: center;
5412
+ justify-content: space-between;
5413
+ color: #94A3B8;
5414
+ font-size: 0.74rem;
5415
+ font-weight: 500;
5416
+ }
5417
+ .editor-container {
5418
+ flex: 1;
5419
+ display: flex;
5420
+ position: relative;
5421
+ overflow: hidden;
5422
+ background: var(--mf-editor-bg);
5423
+ }
5424
+ .line-numbers {
5425
+ width: 44px;
5426
+ padding: 0.8rem 0.4rem;
5427
+ font-family: 'JetBrains Mono', monospace;
5428
+ font-size: 13px;
5429
+ line-height: 1.55;
5430
+ color: #475569;
5431
+ text-align: right;
5432
+ user-select: none;
5433
+ background: var(--mf-editor-gutter);
5434
+ overflow: hidden;
5435
+ border-right: 1px solid #1E293B;
5436
+ }
5437
+ .code-editor {
5438
+ flex: 1;
5439
+ padding: 0.8rem 1rem;
5440
+ font-family: 'JetBrains Mono', monospace;
5441
+ font-size: 13px;
5442
+ line-height: 1.55;
5443
+ color: var(--mf-editor-text);
5444
+ background: transparent;
5445
+ border: none;
5446
+ outline: none;
5447
+ resize: none;
5448
+ white-space: pre;
5449
+ overflow-wrap: normal;
5450
+ overflow: auto;
5451
+ tab-size: 2;
5452
+ }
5453
+
5454
+ /* Draggable Splitter Handle */
5455
+ .splitter {
5456
+ width: 8px;
5457
+ cursor: col-resize;
5458
+ background: #E2E8F0;
5459
+ transition: background 0.15s ease;
5460
+ position: relative;
5461
+ z-index: 5;
5462
+ }
5463
+ .splitter:hover, .splitter.active {
5464
+ background: var(--mf-primary);
5465
+ }
5466
+
5467
+ /* Right Preview Pane */
5468
+ .preview-pane {
5469
+ width: 50%;
5470
+ height: 100%;
5471
+ display: flex;
5472
+ flex-direction: column;
5473
+ background: #FFFFFF;
5474
+ overflow: hidden;
5475
+ }
5476
+ .preview-header {
5477
+ background: #FFFFFF;
5478
+ border-bottom: 1px solid var(--mf-light-border);
5479
+ padding: 0.35rem 0.8rem;
5480
+ display: flex;
5481
+ align-items: center;
5482
+ justify-content: space-between;
5483
+ color: var(--mf-muted);
5484
+ font-size: 0.74rem;
5485
+ font-weight: 600;
5486
+ }
5487
+ .viewport-selector {
5488
+ display: flex;
5489
+ gap: 0.25rem;
5490
+ }
5491
+ .vp-btn {
5492
+ font-size: 0.72rem;
5493
+ padding: 0.15rem 0.4rem;
5494
+ border: 1px solid var(--mf-light-border);
5495
+ background: #F8FAFC;
5496
+ border-radius: 4px;
5497
+ cursor: pointer;
5498
+ color: var(--mf-muted);
5499
+ }
5500
+ .vp-btn.active {
5501
+ background: var(--mf-primary-light);
5502
+ color: var(--mf-primary-dark);
5503
+ border-color: var(--mf-primary-border);
5504
+ }
5505
+ .preview-wrapper {
5506
+ flex: 1;
5507
+ display: flex;
5508
+ justify-content: center;
5509
+ align-items: stretch;
5510
+ background: #F1F5F9;
5511
+ overflow: hidden;
5512
+ }
5513
+ iframe {
5514
+ width: 100%;
5515
+ height: 100%;
5516
+ border: none;
5517
+ background: #FFFFFF;
5518
+ transition: max-width 0.2s ease;
5519
+ }
5520
+ .author-footer {
5521
+ font-size: 0.72rem;
5522
+ color: var(--mf-muted);
5523
+ padding-right: 0.5rem;
5524
+ }
5525
+ .author-footer a {
5526
+ color: var(--mf-primary-dark);
5527
+ text-decoration: none;
5528
+ font-weight: 600;
5529
+ }
5530
+ </style>
5531
+ </head>
5532
+ <body>
5533
+ <header>
5534
+ <div class="brand-section">
5535
+ <span class="brand-badge">MARKFORGE STUDIO</span>
5536
+ <span class="file-name" title="${escapeHtml2(absoluteFilePath)}">${escapeHtml2(fileName)}</span>
5537
+ <div class="sync-status">
5538
+ <div class="sync-dot"></div>
5539
+ <span>Live Sync Active</span>
5540
+ </div>
5541
+ </div>
5542
+
5543
+ <!-- Quick Formatting Toolbar -->
5544
+ <div class="toolbar-section">
5545
+ <button class="tool-btn" onclick="insertFormat('h1')" title="Heading 1">H1</button>
5546
+ <button class="tool-btn" onclick="insertFormat('h2')" title="Heading 2">H2</button>
5547
+ <button class="tool-btn" onclick="insertFormat('h3')" title="Heading 3">H3</button>
5548
+ <div class="tool-divider"></div>
5549
+ <button class="tool-btn" onclick="insertFormat('bold')" title="Bold">B</button>
5550
+ <button class="tool-btn" onclick="insertFormat('italic')" title="Italic">I</button>
5551
+ <button class="tool-btn" onclick="insertFormat('code')" title="Inline Code">&lt;&gt;</button>
5552
+ <button class="tool-btn" onclick="insertFormat('quote')" title="Blockquote">&gt;</button>
5553
+ <div class="tool-divider"></div>
5554
+ <button class="tool-btn" onclick="insertFormat('table')" title="GFM Table">Table</button>
5555
+ <button class="tool-btn" onclick="insertFormat('list')" title="List">List</button>
5556
+ <button class="tool-btn" onclick="insertFormat('task')" title="Task Checklist">Task</button>
5557
+ <div class="tool-divider"></div>
5558
+ <button class="tool-btn" onclick="insertFormat('callout')" title="Callout Box">Callout</button>
5559
+ <button class="tool-btn" onclick="insertFormat('math')" title="LaTeX Math">Math</button>
5560
+ <button class="tool-btn" onclick="insertFormat('columns')" title="Multi-Columns">Columns</button>
5561
+ <button class="tool-btn" onclick="insertFormat('footnote')" title="Footnote">Footnote</button>
5562
+ <button class="tool-btn" onclick="insertFormat('mermaid')" title="Mermaid Diagram">Mermaid</button>
5563
+ </div>
5564
+
5565
+ <!-- Controls & View Mode -->
5566
+ <div class="controls">
5567
+ <div class="view-toggles">
5568
+ <button class="toggle-btn active" id="btn-split" onclick="setViewMode('split')">Split</button>
5569
+ <button class="toggle-btn" id="btn-edit" onclick="setViewMode('edit')">Editor</button>
5570
+ <button class="toggle-btn" id="btn-prev" onclick="setViewMode('prev')">Preview</button>
5571
+ </div>
5572
+ <span class="save-indicator saved" id="save-status">Saved</span>
5573
+ <button class="btn btn-primary" onclick="saveContentManual()" title="Save (Ctrl+S)">Save</button>
5574
+ <button class="btn" onclick="exportDoc('docx')" title="Download Word Document">DOCX</button>
5575
+ <button class="btn" onclick="exportDoc('pdf')" title="Download PDF Document">PDF</button>
5576
+ <button class="btn" onclick="printDoc()" title="Print / PDF dialog">Print</button>
5577
+ </div>
5578
+ </header>
5579
+
5580
+ <main class="workspace" id="workspace">
5581
+ <!-- Left: Code Editor Pane -->
5582
+ <div class="editor-pane" id="editor-pane">
5583
+ <div class="editor-header">
5584
+ <span>MARKDOWN SOURCE</span>
5585
+ <span id="editor-stats">Lines: 1 | Words: 0 | UTF-8</span>
5586
+ </div>
5587
+ <div class="editor-container">
5588
+ <div class="line-numbers" id="line-numbers">1</div>
5589
+ <textarea class="code-editor" id="code-editor" spellcheck="false" placeholder="Write markdown here...">${escapeHtml2(initialContent)}</textarea>
5590
+ </div>
5591
+ </div>
5592
+
5593
+ <!-- Middle: Draggable Splitter Handle -->
5594
+ <div class="splitter" id="splitter"></div>
5595
+
5596
+ <!-- Right: Rendered Preview Pane -->
5597
+ <div class="preview-pane" id="preview-pane">
5598
+ <div class="preview-header">
5599
+ <span>RENDERED PREVIEW</span>
5600
+ <div class="viewport-selector">
5601
+ <button class="vp-btn active" onclick="setViewport('100%')" id="vp-full">100% Full</button>
5602
+ <button class="vp-btn" onclick="setViewport('820px')" id="vp-a4">A4 (820px)</button>
5603
+ <button class="vp-btn" onclick="setViewport('440px')" id="vp-mob">Mobile</button>
5604
+ </div>
5605
+ <span class="author-footer">Created by <a href="https://github.com/masumrpg" target="_blank">Ma'sum (@masumrpg)</a></span>
5606
+ </div>
5607
+ <div class="preview-wrapper">
5608
+ <iframe id="preview-frame" src="/document-content"></iframe>
5609
+ </div>
5610
+ </div>
5611
+ </main>
5612
+
5613
+ <script>
5614
+ var editor = document.getElementById('code-editor');
5615
+ var lineNumbers = document.getElementById('line-numbers');
5616
+ var stats = document.getElementById('editor-stats');
5617
+ var saveStatus = document.getElementById('save-status');
5618
+ var previewFrame = document.getElementById('preview-frame');
5619
+ var editorPane = document.getElementById('editor-pane');
5620
+ var previewPane = document.getElementById('preview-pane');
5621
+ var splitter = document.getElementById('splitter');
5622
+ var isDirty = false;
5623
+ var autoSaveTimeout = null;
5624
+
5625
+ // Update Line Numbers & Stats
5626
+ function updateStatsAndLines() {
5627
+ var lines = editor.value.split('\\n');
5628
+ var lineCount = lines.length;
5629
+ var numHtml = '';
5630
+ for (var i = 1; i <= lineCount; i++) {
5631
+ numHtml += i + '<br>';
5632
+ }
5633
+ lineNumbers.innerHTML = numHtml;
5634
+
5635
+ var words = editor.value.trim().length > 0 ? editor.value.trim().split(/\\s+/).length : 0;
5636
+ var chars = editor.value.length;
5637
+ stats.textContent = 'Lines: ' + lineCount + ' | Words: ' + words + ' | Chars: ' + chars + ' | UTF-8';
5638
+ }
5639
+
5640
+ // Synchronize vertical scroll between Line Numbers and Textarea
5641
+ editor.addEventListener('scroll', function() {
5642
+ lineNumbers.scrollTop = editor.scrollTop;
5643
+ });
5644
+
5645
+ // Handle Input & Debounced Auto-Save
5646
+ editor.addEventListener('input', function() {
5647
+ updateStatsAndLines();
5648
+ setSaveState('unsaved');
5649
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5650
+ autoSaveTimeout = setTimeout(function() {
5651
+ saveContent();
5652
+ }, 600);
5653
+ });
5654
+
5655
+ function setSaveState(state) {
5656
+ if (state === 'saved') {
5657
+ saveStatus.textContent = 'Saved';
5658
+ saveStatus.className = 'save-indicator saved';
5659
+ isDirty = false;
5660
+ } else if (state === 'saving') {
5661
+ saveStatus.textContent = 'Saving...';
5662
+ saveStatus.className = 'save-indicator saving';
5663
+ } else {
5664
+ saveStatus.textContent = 'Changes...';
5665
+ saveStatus.className = 'save-indicator unsaved';
5666
+ isDirty = true;
5667
+ }
5668
+ }
5669
+
5670
+ // Save Content via API
5671
+ function saveContent(callback) {
5672
+ setSaveState('saving');
5673
+ fetch('/api/save-content', {
5674
+ method: 'POST',
5675
+ headers: { 'Content-Type': 'application/json' },
5676
+ body: JSON.stringify({ content: editor.value }),
5677
+ })
5678
+ .then(function(res) { return res.json(); })
5679
+ .then(function(data) {
5680
+ if (data.success) {
5681
+ setSaveState('saved');
5682
+ if (callback) callback();
5683
+ } else {
5684
+ saveStatus.textContent = 'Save Error';
5685
+ }
5686
+ })
5687
+ .catch(function() {
5688
+ saveStatus.textContent = 'Save Error';
5689
+ });
5690
+ }
5691
+
5692
+ function saveContentManual() {
5693
+ saveContent();
5694
+ }
5695
+
5696
+ // Keyboard Shortcuts: Tab (2 spaces), Shift+Tab, Ctrl+S
5697
+ editor.addEventListener('keydown', function(e) {
5698
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
5699
+ e.preventDefault();
5700
+ saveContent();
5701
+ return;
5702
+ }
5703
+
5704
+ if (e.key === 'Tab') {
5705
+ e.preventDefault();
5706
+ var start = this.selectionStart;
5707
+ var end = this.selectionEnd;
5708
+ this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
5709
+ this.selectionStart = this.selectionEnd = start + 2;
5710
+ updateStatsAndLines();
5711
+ setSaveState('unsaved');
5712
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5713
+ autoSaveTimeout = setTimeout(saveContent, 600);
5714
+ }
5715
+ });
5716
+
5717
+ // Formatting Snippet Injector
5718
+ function insertFormat(type) {
5719
+ var start = editor.selectionStart;
5720
+ var end = editor.selectionEnd;
5721
+ var selected = editor.value.substring(start, end);
5722
+ var replacement = '';
5723
+
5724
+ switch (type) {
5725
+ case 'h1': replacement = '# ' + (selected || 'Heading 1'); break;
5726
+ case 'h2': replacement = '## ' + (selected || 'Heading 2'); break;
5727
+ case 'h3': replacement = '### ' + (selected || 'Heading 3'); break;
5728
+ case 'bold': replacement = '**' + (selected || 'bold text') + '**'; break;
5729
+ case 'italic': replacement = '*' + (selected || 'italic text') + '*'; break;
5730
+ case 'code': replacement = '\`' + (selected || 'inline code') + '\`'; break;
5731
+ case 'quote': replacement = '> ' + (selected || 'Quote text'); break;
5732
+ case 'table':
5733
+ replacement = '\\n| Column 1 | Column 2 | Column 3 |\\n| :--- | :---: | ---: |\\n| Data A | Data B | Data C |\\n| Data D | Data E | Data F |\\n';
5734
+ break;
5735
+ case 'list': replacement = '- ' + (selected || 'List item'); break;
5736
+ case 'task': replacement = '- [ ] ' + (selected || 'Task item'); break;
5737
+ case 'callout':
5738
+ replacement = '> [!NOTE]\\n> ' + (selected || 'This is an important callout note.');
5739
+ break;
5740
+ case 'math':
5741
+ replacement = '$$\\n' + (selected || '\\\\int_{-\\\\infty}^{\\\\infty} e^{-x^2} dx = \\\\sqrt{\\\\pi}') + '\\n$$';
5742
+ break;
5743
+ case 'columns':
5744
+ replacement = ':::columns 2\\n:::col\\n### Left Column\\n' + (selected || 'Content on the left.') + '\\n:::\\n:::col\\n### Right Column\\nContent on the right.\\n:::\\n:::';
5745
+ break;
5746
+ case 'footnote':
5747
+ replacement = (selected || 'Statement with footnote') + '[^1]\\n\\n[^1]: Note description text.';
5748
+ break;
5749
+ case 'mermaid':
5750
+ replacement = '\\n\`\`\`mermaid\\ngraph TD\\n A[Start] --> B(Process)\\n B --> C{Decision}\\n C -->|Yes| D[Done]\\n C -->|No| B\\n\`\`\`\\n';
5751
+ break;
5752
+ }
5753
+
5754
+ editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
5755
+ editor.selectionStart = editor.selectionEnd = start + replacement.length;
5756
+ editor.focus();
5757
+ updateStatsAndLines();
5758
+ setSaveState('unsaved');
5759
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5760
+ autoSaveTimeout = setTimeout(saveContent, 600);
5761
+ }
5762
+
5763
+ // View Mode Toggle (Split / Editor Only / Preview Only)
5764
+ function setViewMode(mode) {
5765
+ document.getElementById('btn-split').classList.remove('active');
5766
+ document.getElementById('btn-edit').classList.remove('active');
5767
+ document.getElementById('btn-prev').classList.remove('active');
5768
+
5769
+ if (mode === 'split') {
5770
+ document.getElementById('btn-split').classList.add('active');
5771
+ editorPane.style.display = 'flex';
5772
+ editorPane.style.width = '50%';
5773
+ previewPane.style.display = 'flex';
5774
+ previewPane.style.width = '50%';
5775
+ splitter.style.display = 'block';
5776
+ } else if (mode === 'edit') {
5777
+ document.getElementById('btn-edit').classList.add('active');
5778
+ editorPane.style.display = 'flex';
5779
+ editorPane.style.width = '100%';
5780
+ previewPane.style.display = 'none';
5781
+ splitter.style.display = 'none';
5782
+ } else if (mode === 'prev') {
5783
+ document.getElementById('btn-prev').classList.add('active');
5784
+ editorPane.style.display = 'none';
5785
+ previewPane.style.display = 'flex';
5786
+ previewPane.style.width = '100%';
5787
+ splitter.style.display = 'none';
5788
+ }
5789
+ }
5790
+
5791
+ // Viewport Width Resizer
5792
+ function setViewport(width) {
5793
+ document.getElementById('vp-full').classList.remove('active');
5794
+ document.getElementById('vp-a4').classList.remove('active');
5795
+ document.getElementById('vp-mob').classList.remove('active');
5796
+
5797
+ if (width === '100%') {
5798
+ document.getElementById('vp-full').classList.add('active');
5799
+ previewFrame.style.maxWidth = '100%';
5800
+ } else if (width === '820px') {
5801
+ document.getElementById('vp-a4').classList.add('active');
5802
+ previewFrame.style.maxWidth = '820px';
5803
+ } else if (width === '440px') {
5804
+ document.getElementById('vp-mob').classList.add('active');
5805
+ previewFrame.style.maxWidth = '440px';
5806
+ }
5807
+ }
5808
+
5809
+ // Draggable Splitter Handle Logic
5810
+ var isDragging = false;
5811
+ splitter.addEventListener('mousedown', function(e) {
5812
+ isDragging = true;
5813
+ splitter.classList.add('active');
5814
+ document.body.style.cursor = 'col-resize';
5815
+ document.body.style.userSelect = 'none';
5816
+ });
5817
+
5818
+ window.addEventListener('mousemove', function(e) {
5819
+ if (!isDragging) return;
5820
+ var totalWidth = document.getElementById('workspace').clientWidth;
5821
+ var newEditorWidth = (e.clientX / totalWidth) * 100;
5822
+ if (newEditorWidth > 15 && newEditorWidth < 85) {
5823
+ editorPane.style.width = newEditorWidth + '%';
5824
+ previewPane.style.width = (100 - newEditorWidth) + '%';
5825
+ }
5826
+ });
5827
+
5828
+ window.addEventListener('mouseup', function() {
5829
+ if (isDragging) {
5830
+ isDragging = false;
5831
+ splitter.classList.remove('active');
5832
+ document.body.style.cursor = '';
5833
+ document.body.style.userSelect = '';
5834
+ }
5835
+ });
5836
+
5837
+ // Document Print Action
5838
+ function printDoc() {
5839
+ previewFrame.contentWindow.print();
5840
+ }
5841
+
5842
+ // Document Export Action
5843
+ function exportDoc(format) {
5844
+ saveContent(function() {
5845
+ window.location.href = '/api/export?format=' + format;
5846
+ });
5847
+ }
5848
+
5849
+ // Initialize line stats
5850
+ updateStatsAndLines();
5851
+ </script>
5852
+ </body>
5853
+ </html>`;
5854
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5855
+ res.end(appHtml);
5856
+ return;
5857
+ }
5858
+ res.writeHead(404, { "Content-Type": "text/plain" });
5859
+ res.end("Not Found");
5860
+ });
5861
+ return new Promise((resolve6, reject) => {
5862
+ server.listen(port, () => {
5863
+ const url = `http://localhost:${port}`;
5864
+ resolve6({
5865
+ server,
5866
+ port,
5867
+ url,
5868
+ close: async () => {
5869
+ watcher.close();
5870
+ sseClients.forEach((client) => {
5871
+ try {
5872
+ client.end();
5873
+ } catch {
5874
+ }
5875
+ });
5876
+ sseClients.clear();
5877
+ return new Promise((res) => {
5878
+ server.close(() => res());
5879
+ });
5880
+ }
5881
+ });
5882
+ });
5883
+ server.on("error", (err) => {
5884
+ reject(err);
5885
+ });
5886
+ });
5887
+ }
5888
+ function escapeHtml2(str) {
5889
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5890
+ }
5891
+
3490
5892
  // src/config/defineConfig.ts
3491
5893
  function defineConfig(config) {
3492
5894
  return config;
3493
5895
  }
3494
5896
 
3495
5897
  // src/version.ts
3496
- import * as fs7 from "fs";
3497
- import * as path7 from "path";
5898
+ import * as fs8 from "fs";
5899
+ import * as path8 from "path";
3498
5900
  import { fileURLToPath } from "url";
3499
5901
  try {
3500
5902
  if (typeof globalThis !== "undefined" && (!globalThis.localStorage || typeof globalThis.localStorage.getItem !== "function")) {
@@ -3516,32 +5918,33 @@ try {
3516
5918
  }
3517
5919
  } catch {
3518
5920
  }
3519
- var FALLBACK_VERSION = "0.3.0";
3520
5921
  function readVersionFromPackageJson(fromDir) {
3521
5922
  let currentDir = fromDir;
3522
- for (let i = 0; i < 6; i++) {
5923
+ for (let i = 0; i < 10; i++) {
3523
5924
  try {
3524
- const pkgJsonPath = path7.join(currentDir, "package.json");
3525
- if (fs7.existsSync(pkgJsonPath)) {
3526
- const pkg = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5925
+ const pkgJsonPath = path8.join(currentDir, "package.json");
5926
+ if (fs8.existsSync(pkgJsonPath)) {
5927
+ const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
3527
5928
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
3528
5929
  return pkg.version;
3529
5930
  }
3530
5931
  }
3531
5932
  } catch {
3532
5933
  }
3533
- const parentDir = path7.dirname(currentDir);
5934
+ const parentDir = path8.dirname(currentDir);
3534
5935
  if (parentDir === currentDir) break;
3535
5936
  currentDir = parentDir;
3536
5937
  }
3537
- return FALLBACK_VERSION;
5938
+ throw new Error(
5939
+ "Failed to resolve '@masumdev/markforge' package version: package.json was not found or is missing a valid 'version' field."
5940
+ );
3538
5941
  }
3539
5942
  function getPackageDir() {
3540
5943
  if (typeof __dirname !== "undefined") {
3541
5944
  return __dirname;
3542
5945
  }
3543
5946
  try {
3544
- return path7.dirname(fileURLToPath(import.meta.url));
5947
+ return path8.dirname(fileURLToPath(import.meta.url));
3545
5948
  } catch {
3546
5949
  return process.cwd();
3547
5950
  }
@@ -3552,6 +5955,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3552
5955
  }
3553
5956
  export {
3554
5957
  DEFAULT_CONFIG,
5958
+ KATEX_INLINE_CSS,
3555
5959
  MARKFORGE_VERSION,
3556
5960
  Orientation,
3557
5961
  OutputFormat,
@@ -3564,6 +5968,7 @@ export {
3564
5968
  THEME_DEFAULT,
3565
5969
  Theme,
3566
5970
  WatermarkPosition,
5971
+ applyHeadingNumbering,
3567
5972
  buildDocxDocument,
3568
5973
  buildHtmlDocument,
3569
5974
  buildPdfDocument,
@@ -3580,18 +5985,28 @@ export {
3580
5985
  inlineHtmlImages,
3581
5986
  loadConfig,
3582
5987
  compileMarkdown as markforge,
5988
+ normalizeBackCover,
5989
+ normalizeCoverPage,
3583
5990
  normalizeHeaderFooter,
3584
5991
  normalizeHeaderFooterSlot,
5992
+ normalizeNumberHeadings,
5993
+ normalizeSecurity,
3585
5994
  normalizeSignatures,
3586
5995
  normalizeWatermark,
3587
5996
  parseInlineSpans,
3588
5997
  parseMarginToTwip2 as parseMarginToTwip,
5998
+ parseMarkdownDocument as parseMarkdown,
3589
5999
  parseMarkdownDocument,
6000
+ renderBackCoverHtml,
6001
+ renderCoverPageHtml,
3590
6002
  renderInlinesToHtml,
6003
+ renderMathToHtml,
3591
6004
  renderMermaidToPng,
6005
+ renderNodesToHtml,
3592
6006
  replaceDocumentTokens,
3593
6007
  resolveDocumentConfig,
3594
6008
  resolveImage,
3595
6009
  slugify,
6010
+ startPreviewServer,
3596
6011
  tokenizeCodeLine
3597
6012
  };