@speajus/markdown-to-pdf 1.0.19 → 1.0.20
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/README.pdf +0 -0
- package/dist/renderer.js +88 -10
- package/package.json +1 -1
package/README.pdf
CHANGED
|
Binary file
|
package/dist/renderer.js
CHANGED
|
@@ -411,6 +411,21 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
411
411
|
doc.moveDown(0.5);
|
|
412
412
|
break;
|
|
413
413
|
}
|
|
414
|
+
case 'html': {
|
|
415
|
+
const htmlText = (tok.text ?? tok.raw ?? '').trim();
|
|
416
|
+
if (/^<br\s*\/?>$/i.test(htmlText)) {
|
|
417
|
+
doc.moveDown(0.5);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Non-br HTML: render as text (fall through to default behavior)
|
|
421
|
+
const raw = tok.text ?? tok.raw ?? '';
|
|
422
|
+
if (raw) {
|
|
423
|
+
applyBodyFont(insideBold, insideItalic);
|
|
424
|
+
renderText(raw, { continued: cont, underline: false, strike: false });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
414
429
|
default: {
|
|
415
430
|
const raw = tok.text ?? tok.raw ?? '';
|
|
416
431
|
if (raw) {
|
|
@@ -449,6 +464,7 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
449
464
|
if (linkUrl) {
|
|
450
465
|
doc.link(imgX, imgY, displayWidth, displayHeight, linkUrl);
|
|
451
466
|
}
|
|
467
|
+
doc.y = imgY + displayHeight;
|
|
452
468
|
doc.moveDown(0.5);
|
|
453
469
|
}
|
|
454
470
|
catch {
|
|
@@ -504,39 +520,101 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
504
520
|
}
|
|
505
521
|
doc.y = savedY;
|
|
506
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Extract plain text from cell tokens, converting <br> HTML tokens to \n
|
|
525
|
+
* so that heightOfString can measure the full multiline content.
|
|
526
|
+
*/
|
|
527
|
+
function cellPlainText(cell) {
|
|
528
|
+
if (!cell.tokens || cell.tokens.length === 0)
|
|
529
|
+
return cell.text;
|
|
530
|
+
function extract(tokens) {
|
|
531
|
+
let result = '';
|
|
532
|
+
for (const tok of tokens) {
|
|
533
|
+
if (tok.type === 'br') {
|
|
534
|
+
result += '\n';
|
|
535
|
+
}
|
|
536
|
+
else if (tok.type === 'html') {
|
|
537
|
+
const raw = (tok.text ?? tok.raw ?? '').trim();
|
|
538
|
+
if (/^<br\s*\/?>$/i.test(raw)) {
|
|
539
|
+
result += '\n';
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
result += tok.text ?? tok.raw ?? '';
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else if (tok.tokens && tok.tokens.length > 0) {
|
|
546
|
+
result += extract(tok.tokens);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
result += tok.text ?? tok.raw ?? '';
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
return extract(cell.tokens);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Measure the height a cell needs given its content and available width.
|
|
558
|
+
*/
|
|
559
|
+
function measureCellHeight(cell, cellWidth, bold) {
|
|
560
|
+
const text = cellPlainText(cell);
|
|
561
|
+
const font = bold
|
|
562
|
+
? safeFont(resolveFont(theme.body.font, true, false))
|
|
563
|
+
: safeFont(theme.body.font);
|
|
564
|
+
doc.font(font).fontSize(theme.body.fontSize);
|
|
565
|
+
return doc.heightOfString(text, { width: cellWidth });
|
|
566
|
+
}
|
|
507
567
|
async function renderTable(table) {
|
|
508
568
|
const colCount = table.header.length;
|
|
509
569
|
if (colCount === 0)
|
|
510
570
|
return;
|
|
511
571
|
const cellPad = theme.table.cellPadding;
|
|
512
572
|
const colWidth = contentWidth / colCount;
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
ensureSpace(
|
|
573
|
+
const minRowH = theme.body.fontSize + cellPad * 2 + 4;
|
|
574
|
+
const textWidth = colWidth - cellPad * 2;
|
|
575
|
+
ensureSpace(minRowH * 2);
|
|
516
576
|
const startX = margins.left;
|
|
517
577
|
let y = doc.y;
|
|
518
|
-
//
|
|
578
|
+
// ── Measure header row height ──
|
|
579
|
+
let headerH = minRowH;
|
|
580
|
+
let maxHeaderTextHeight = 0;
|
|
581
|
+
for (let c = 0; c < colCount; c++) {
|
|
582
|
+
const h = measureCellHeight(table.header[c], textWidth, true);
|
|
583
|
+
maxHeaderTextHeight = Math.max(maxHeaderTextHeight, h);
|
|
584
|
+
headerH = Math.max(headerH, h + cellPad * 2 + 4);
|
|
585
|
+
}
|
|
586
|
+
const headerTextInsetY = (headerH - maxHeaderTextHeight) / 2;
|
|
587
|
+
// Header row background
|
|
519
588
|
doc.save();
|
|
520
|
-
doc.rect(startX, y, contentWidth,
|
|
589
|
+
doc.rect(startX, y, contentWidth, headerH).fill(theme.table.headerBackground);
|
|
521
590
|
doc.restore();
|
|
522
591
|
for (let c = 0; c < colCount; c++) {
|
|
523
592
|
const cellX = startX + c * colWidth;
|
|
524
|
-
await renderCellTokens(table.header[c], cellX + cellPad, y +
|
|
593
|
+
await renderCellTokens(table.header[c], cellX + cellPad, y + headerTextInsetY, textWidth, table.align[c] || 'left', true);
|
|
525
594
|
}
|
|
526
595
|
// Header border
|
|
527
596
|
doc.save();
|
|
528
597
|
doc.strokeColor(theme.table.borderColor).lineWidth(0.5);
|
|
529
|
-
doc.rect(startX, y, contentWidth,
|
|
598
|
+
doc.rect(startX, y, contentWidth, headerH).stroke();
|
|
530
599
|
for (let c = 1; c < colCount; c++) {
|
|
531
600
|
const cx = startX + c * colWidth;
|
|
532
|
-
doc.moveTo(cx, y).lineTo(cx, y +
|
|
601
|
+
doc.moveTo(cx, y).lineTo(cx, y + headerH).stroke();
|
|
533
602
|
}
|
|
534
603
|
doc.restore();
|
|
535
|
-
y +=
|
|
604
|
+
y += headerH;
|
|
536
605
|
// Body rows
|
|
537
606
|
const zebraColor = theme.table.zebraColor ?? '#f9f9f9';
|
|
538
607
|
for (let r = 0; r < table.rows.length; r++) {
|
|
539
608
|
const row = table.rows[r];
|
|
609
|
+
// ── Measure row height ──
|
|
610
|
+
let rowH = minRowH;
|
|
611
|
+
let maxRowTextHeight = 0;
|
|
612
|
+
for (let c = 0; c < colCount; c++) {
|
|
613
|
+
const h = measureCellHeight(row[c], textWidth, false);
|
|
614
|
+
maxRowTextHeight = Math.max(maxRowTextHeight, h);
|
|
615
|
+
rowH = Math.max(rowH, h + cellPad * 2 + 4);
|
|
616
|
+
}
|
|
617
|
+
const textInsetY = (rowH - maxRowTextHeight) / 2;
|
|
540
618
|
doc.y = y; // sync doc.y BEFORE ensureSpace check
|
|
541
619
|
ensureSpace(rowH);
|
|
542
620
|
y = doc.y; // re-sync AFTER possible page break
|
|
@@ -548,7 +626,7 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
548
626
|
}
|
|
549
627
|
for (let c = 0; c < colCount; c++) {
|
|
550
628
|
const cellX = startX + c * colWidth;
|
|
551
|
-
await renderCellTokens(row[c], cellX + cellPad, y + textInsetY,
|
|
629
|
+
await renderCellTokens(row[c], cellX + cellPad, y + textInsetY, textWidth, table.align[c] || 'left', false);
|
|
552
630
|
}
|
|
553
631
|
doc.save();
|
|
554
632
|
doc.strokeColor(theme.table.borderColor).lineWidth(0.5);
|