Files
breakpilot-compliance/admin-compliance/lib/sdk/einwilligungen/export/pdf-helpers.ts
Sharang Parnerkar 7d8e5667c9 refactor(admin-compliance): split 7 oversized files under 500 LOC hard cap (batch 3)
- tom-generator/export/zip.ts: extract private helpers to zip-helpers.ts (544→342 LOC)
- tom-generator/export/docx.ts: extract private helpers to docx-helpers.ts (525→378 LOC)
- tom-generator/export/pdf.ts: extract private helpers to pdf-helpers.ts (517→446 LOC)
- tom-generator/demo-data/index.ts: extract DEMO_RISK_PROFILES + DEMO_EVIDENCE_DOCUMENTS to demo-data-part2.ts (518→360 LOC)
- einwilligungen/generator/privacy-policy-sections.ts: extract sections 5-7 to part2 (559→313 LOC)
- einwilligungen/export/pdf.ts: extract HTML/CSS helpers to pdf-helpers.ts (505→296 LOC)
- vendor-compliance/context.tsx: extract API action hooks to context-actions.tsx (509→286 LOC)

All originals re-export from sibling files — zero consumer import changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:43:41 +02:00

219 lines
5.1 KiB
TypeScript

// =============================================================================
// Privacy Policy PDF Export - Helper Functions
// Private helpers extracted from pdf.ts to stay under 500 LOC hard cap
// =============================================================================
import type { PDFExportOptions, PDFSection } from './pdf'
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
export function generateHTMLFromContent(
content: PDFSection[],
options: PDFExportOptions
): string {
const pageWidth = options.pageSize === 'A4' ? '210mm' : '8.5in'
const pageHeight = options.pageSize === 'A4' ? '297mm' : '11in'
let html = `
<!DOCTYPE html>
<html lang="${options.language}">
<head>
<meta charset="utf-8">
<title>${options.language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'}</title>
<style>
@page {
size: ${pageWidth} ${pageHeight};
margin: 20mm;
}
body {
font-family: 'Segoe UI', Calibri, Arial, sans-serif;
font-size: ${options.fontSize}pt;
line-height: 1.6;
color: #1e293b;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
font-size: 24pt;
color: ${options.primaryColor};
border-bottom: 2px solid ${options.primaryColor};
padding-bottom: 10px;
margin-top: 30px;
}
h2 {
font-size: 16pt;
color: ${options.primaryColor};
margin-top: 24px;
}
h3 {
font-size: 13pt;
color: #334155;
margin-top: 18px;
}
p {
margin: 12px 0;
text-align: justify;
}
.title {
font-size: 28pt;
text-align: center;
color: ${options.primaryColor};
font-weight: bold;
margin-bottom: 10px;
}
.subtitle {
text-align: center;
font-style: italic;
color: #64748b;
}
.center {
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 10pt;
}
th, td {
border: 1px solid #e2e8f0;
padding: 8px 12px;
text-align: left;
}
th {
background-color: ${options.primaryColor};
color: white;
font-weight: 600;
}
tr:nth-child(even) {
background-color: #f8fafc;
}
ul {
margin: 12px 0;
padding-left: 24px;
}
li {
margin: 6px 0;
}
.pagebreak {
page-break-after: always;
}
.footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #e2e8f0;
font-size: 9pt;
color: #94a3b8;
text-align: center;
}
@media print {
body {
padding: 0;
}
.pagebreak {
page-break-after: always;
}
}
</style>
</head>
<body>
`
for (const section of content) {
switch (section.type) {
case 'title':
html += `<div class="title" style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</div>\n`
break
case 'heading':
html += `<h1 style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</h1>\n`
break
case 'subheading':
html += `<h3 style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</h3>\n`
break
case 'paragraph': {
const alignClass = section.style?.align === 'center' ? ' class="center"' : ''
html += `<p${alignClass} style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</p>\n`
break
}
case 'list':
html += '<ul>\n'
for (const item of section.items || []) {
html += ` <li>${escapeHtml(item)}</li>\n`
}
html += '</ul>\n'
break
case 'table':
if (section.table) {
html += '<table>\n<thead><tr>\n'
for (const header of section.table.headers) {
html += ` <th>${escapeHtml(header)}</th>\n`
}
html += '</tr></thead>\n<tbody>\n'
for (const row of section.table.rows) {
html += '<tr>\n'
for (const cell of row) {
html += ` <td>${escapeHtml(cell)}</td>\n`
}
html += '</tr>\n'
}
html += '</tbody></table>\n'
}
break
case 'pagebreak':
html += '<div class="pagebreak"></div>\n'
break
}
}
html += '</body></html>'
return html
}
export function getStyleString(style?: PDFSection['style']): string {
if (!style) return ''
const parts: string[] = []
if (style.color) parts.push(`color: ${style.color}`)
if (style.fontSize) parts.push(`font-size: ${style.fontSize}pt`)
if (style.bold) parts.push('font-weight: bold')
if (style.italic) parts.push('font-style: italic')
if (style.align) parts.push(`text-align: ${style.align}`)
return parts.join('; ')
}
export function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}