Improve PDF generation by preserving text formatting and updating header layout
Introduce `htmlToSafeHtml` function to retain text formatting (color, bold) in PDFs and restructure the header layout, moving the logo to the right and title to the left.
This commit is contained in:
@@ -118,6 +118,25 @@ function htmlToPlain(input: string | null | undefined): string {
|
||||
.trim();
|
||||
}
|
||||
|
||||
function htmlToSafeHtml(input: string | null | undefined): string {
|
||||
if (!input) return "";
|
||||
const withBreaks = input
|
||||
.replace(/<\/(p|div)>/gi, "<br/>")
|
||||
.replace(/<(p|div)(\s[^>]*)?>/gi, "");
|
||||
const sanitized = sanitizeHtml(withBreaks, {
|
||||
allowedTags: ["span", "br", "strong", "b", "em", "i"],
|
||||
allowedAttributes: { span: ["style"] },
|
||||
allowedStyles: {
|
||||
span: {
|
||||
color: [/.*/],
|
||||
"font-weight": [/.*/],
|
||||
"background-color": [/.*/],
|
||||
},
|
||||
},
|
||||
});
|
||||
return sanitized.replace(/(<br\s*\/?>)+$/gi, "").trim();
|
||||
}
|
||||
|
||||
function formatTimeRange(
|
||||
startTime: string | null,
|
||||
endTime: string | null,
|
||||
@@ -199,7 +218,7 @@ function buildAttendeesHtml(attendees: PdfMeetingAttendee[]): string {
|
||||
|
||||
function buildMeetingRow(meeting: PdfMeeting, input: RenderPdfInput): string {
|
||||
const isRtl = input.lang === "ar";
|
||||
const title = htmlToPlain(
|
||||
const titleHtml = htmlToSafeHtml(
|
||||
isRtl ? meeting.titleAr : meeting.titleEn || meeting.titleAr,
|
||||
);
|
||||
const location = meeting.location ? `<br/><span style="font-size:90%">${esc(meeting.location)}</span>` : "";
|
||||
@@ -221,7 +240,7 @@ function buildMeetingRow(meeting: PdfMeeting, input: RenderPdfInput): string {
|
||||
|
||||
const cellsLtr: TableCell[] = [
|
||||
{ html: esc(String(meeting.dailyNumber)), cls: "num-cell", style: numDarkStyle },
|
||||
{ html: esc(title) + location, cls: "meeting-cell", style: coloredStyle },
|
||||
{ html: titleHtml + location, cls: "meeting-cell", style: coloredStyle },
|
||||
{ html: attendeesHtml, cls: "attendees-cell", style: "" },
|
||||
{ html: `<span dir="ltr">${esc(time)}</span>`, cls: "time-cell", style: "" },
|
||||
];
|
||||
@@ -240,9 +259,9 @@ function buildMeetingRow(meeting: PdfMeeting, input: RenderPdfInput): string {
|
||||
const endIdx = MERGE_COL_INDEX[meeting.mergeEndColumn];
|
||||
if (startIdx !== undefined && endIdx !== undefined && startIdx <= endIdx) {
|
||||
const colspan = endIdx - startIdx + 1;
|
||||
const mergeText = meeting.mergeText ? htmlToPlain(meeting.mergeText) : "";
|
||||
const mergeHtml = meeting.mergeText ? htmlToSafeHtml(meeting.mergeText) : "";
|
||||
const mergedCell: TableCell = {
|
||||
html: esc(mergeText),
|
||||
html: mergeHtml,
|
||||
cls: "merged-cell",
|
||||
style: "",
|
||||
colspan,
|
||||
@@ -272,7 +291,7 @@ export function buildScheduleHtml(input: RenderPdfInput): string {
|
||||
const colWidths = ["8%", "30%", "42%", "20%"];
|
||||
|
||||
const logoHtml = input.logo && input.logo.length > 0
|
||||
? `<img src="data:image/png;base64,${input.logo.toString("base64")}" style="height:50px;width:auto;" />`
|
||||
? `<img src="data:image/png;base64,${input.logo.toString("base64")}" />`
|
||||
: "";
|
||||
|
||||
const headerCells = headerLabels
|
||||
@@ -323,27 +342,28 @@ body {
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
min-height: ${logoHtml ? "55px" : "auto"};
|
||||
.pdf-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.page-header .logo {
|
||||
position: absolute;
|
||||
${isRtl ? "right" : "left"}: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
.pdf-title {
|
||||
color: #002060;
|
||||
font-size: ${Math.min(22, fontSize + 6)}px;
|
||||
font-weight: bold;
|
||||
color: ${fontColor};
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pdf-logo img {
|
||||
max-height: 50px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -447,9 +467,9 @@ tr {
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
${logoHtml ? `<div class="logo">${logoHtml}</div>` : ""}
|
||||
<h1>${esc(input.labels.title)}</h1>
|
||||
<div class="pdf-header">
|
||||
<h1 class="pdf-title">${esc(input.labels.title)}</h1>
|
||||
${logoHtml ? `<div class="pdf-logo">${logoHtml}</div>` : ""}
|
||||
</div>
|
||||
|
||||
<table>
|
||||
|
||||
Reference in New Issue
Block a user