From 8350a1bd5d4d35b7f6472bb99d43498f7bd55802 Mon Sep 17 00:00:00 2001 From: Denis Lugowski Date: Wed, 21 Jan 2026 06:29:33 +0100 Subject: [PATCH] feat(#35): Handle wide tables better in PDF preview --- .../ApplicationFormFormatService.kt | 45 +++++++++++++++---- .../export/latex/LatexExportModel.kt | 1 + .../application_form_latex_template.tex | 22 +++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt index c46c85b..271cdaf 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt @@ -77,11 +77,14 @@ class ApplicationFormFormatService( subtitle = LatexEscaper.escape(subsection.subtitle ?: ""), elements = subsection.elements.map { element -> + val isTable = element.type.name == "TABLE" + val tableInfo = if (isTable) renderTableValue(element) else null LatexFormElement( title = LatexEscaper.escape(element.title ?: ""), description = LatexEscaper.escape(element.description ?: ""), - value = renderElementValue(element), - isTable = element.type.name == "TABLE", + value = tableInfo?.first ?: renderElementValue(element), + isTable = isTable, + isWideTable = tableInfo?.second ?: false, ) }, ) @@ -131,13 +134,17 @@ class ApplicationFormFormatService( if (element.options.any { it.value == "true" }) "Ja" else "Nein" } "TABLE" -> { - renderTableValue(element) + renderTableValue(element).first } else -> "Keine Auswahl getroffen" } - private fun renderTableValue(element: FormElementSnapshotDto): String { - if (element.options.isEmpty()) return "Keine Daten" + /** + * Renders a table element to LaTeX. + * @return Pair of (LaTeX string, isWideTable flag) + */ + private fun renderTableValue(element: FormElementSnapshotDto): Pair { + if (element.options.isEmpty()) return "Keine Daten" to false val objectMapper = jacksonObjectMapper() val headers = element.options.map { LatexEscaper.escape(it.label ?: "") } @@ -152,10 +159,15 @@ class ApplicationFormFormatService( } val rowCount = columnData.maxOfOrNull { col -> col.size } ?: 0 - if (rowCount == 0) return "Keine Daten" + if (rowCount == 0) return "Keine Daten" to false + + val columnCount = headers.size + val isWideTable = columnCount > WIDE_TABLE_COLUMN_THRESHOLD // Use tabularx with Y columns (auto-wrapping) for flexible width distribution // Y is defined as >{\raggedright\arraybackslash}X in the template + // For wide tables, use \linewidth (works correctly inside landscape environment) + val tableWidth = if (isWideTable) "\\linewidth" else "\\textwidth" val columnSpec = headers.joinToString("") { "Y" } val headerRow = headers.joinToString(" & ") { "\\textbf{$it}" } val dataRows = @@ -166,8 +178,13 @@ class ApplicationFormFormatService( } } - return buildString { - appendLine("\\begin{tabularx}{\\textwidth}{$columnSpec}") + val latexContent = buildString { + if (isWideTable) { + // Use smaller font and tighter column spacing for wide tables + appendLine("\\footnotesize") + appendLine("\\setlength{\\tabcolsep}{2pt}") + } + appendLine("\\begin{tabularx}{$tableWidth}{$columnSpec}") appendLine("\\toprule") appendLine("$headerRow \\\\") appendLine("\\midrule") @@ -176,7 +193,19 @@ class ApplicationFormFormatService( } appendLine("\\bottomrule") appendLine("\\end{tabularx}") + if (isWideTable) { + // Reset to normal settings after the table + appendLine("\\normalsize") + appendLine("\\setlength{\\tabcolsep}{6pt}") + } } + + return latexContent to isWideTable + } + + companion object { + /** Tables with more than this number of columns are rendered in landscape mode */ + private const val WIDE_TABLE_COLUMN_THRESHOLD = 6 } private fun filterVisibleElements(snapshot: ApplicationFormSnapshotDto): ApplicationFormSnapshotDto { diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/export/latex/LatexExportModel.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/export/latex/LatexExportModel.kt index 3872975..680a239 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/export/latex/LatexExportModel.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/export/latex/LatexExportModel.kt @@ -29,4 +29,5 @@ data class LatexFormElement( val description: String?, val value: String, val isTable: Boolean = false, + val isWideTable: Boolean = false, ) diff --git a/legalconsenthub-backend/src/main/resources/templates/application_form_latex_template.tex b/legalconsenthub-backend/src/main/resources/templates/application_form_latex_template.tex index 3e94596..1a4546e 100644 --- a/legalconsenthub-backend/src/main/resources/templates/application_form_latex_template.tex +++ b/legalconsenthub-backend/src/main/resources/templates/application_form_latex_template.tex @@ -18,10 +18,16 @@ \usepackage{tabularx} \usepackage{array} \usepackage{booktabs} +\usepackage{pdflscape} +\usepackage{scrlayer-scrpage} +\usepackage{geometry} % Define column type for auto-wrapping text \newcolumntype{Y}{>{\raggedright\arraybackslash}X} +% Page style for landscape pages - no header/footer to maximize table space +\newpagestyle{landscapestyle}{}{} + \hypersetup{ colorlinks=true, linkcolor=black, @@ -74,6 +80,21 @@ Dieses Dokument enthält die Details der Betriebsvereinbarung "[[${applicationFo [/] [# th:each="element : ${subsection.elements}"] +[# th:if="${element.isTable && element.isWideTable}"] +\newgeometry{left=1cm,right=1cm,top=1cm,bottom=1cm} +\begin{landscape} +\thispagestyle{landscapestyle} +\paragraph{[[${element.title}]]} +[# th:if="${element.description}"] +\textit{\small [[${element.description}]]} +[/] +\vspace{0.5em} +\noindent +[(${element.value})] +\end{landscape} +\restoregeometry +[/] +[# th:if="${!(element.isTable && element.isWideTable)}"] \paragraph{[[${element.title}]]} [# th:if="${element.description}"] \textit{\small [[${element.description}]]} @@ -95,6 +116,7 @@ Dieses Dokument enthält die Details der Betriebsvereinbarung "[[${applicationFo [/] [/] [/] +[/] \vspace{3cm}