feat(#35): Handle wide tables better in PDF preview

This commit is contained in:
2026-01-21 06:29:33 +01:00
parent 753d90dc64
commit 8350a1bd5d
3 changed files with 60 additions and 8 deletions

View File

@@ -77,11 +77,14 @@ class ApplicationFormFormatService(
subtitle = LatexEscaper.escape(subsection.subtitle ?: ""), subtitle = LatexEscaper.escape(subsection.subtitle ?: ""),
elements = elements =
subsection.elements.map { element -> subsection.elements.map { element ->
val isTable = element.type.name == "TABLE"
val tableInfo = if (isTable) renderTableValue(element) else null
LatexFormElement( LatexFormElement(
title = LatexEscaper.escape(element.title ?: ""), title = LatexEscaper.escape(element.title ?: ""),
description = LatexEscaper.escape(element.description ?: ""), description = LatexEscaper.escape(element.description ?: ""),
value = renderElementValue(element), value = tableInfo?.first ?: renderElementValue(element),
isTable = element.type.name == "TABLE", isTable = isTable,
isWideTable = tableInfo?.second ?: false,
) )
}, },
) )
@@ -131,13 +134,17 @@ class ApplicationFormFormatService(
if (element.options.any { it.value == "true" }) "Ja" else "Nein" if (element.options.any { it.value == "true" }) "Ja" else "Nein"
} }
"TABLE" -> { "TABLE" -> {
renderTableValue(element) renderTableValue(element).first
} }
else -> "Keine Auswahl getroffen" 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<String, Boolean> {
if (element.options.isEmpty()) return "Keine Daten" to false
val objectMapper = jacksonObjectMapper() val objectMapper = jacksonObjectMapper()
val headers = element.options.map { LatexEscaper.escape(it.label ?: "") } val headers = element.options.map { LatexEscaper.escape(it.label ?: "") }
@@ -152,10 +159,15 @@ class ApplicationFormFormatService(
} }
val rowCount = columnData.maxOfOrNull { col -> col.size } ?: 0 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 // Use tabularx with Y columns (auto-wrapping) for flexible width distribution
// Y is defined as >{\raggedright\arraybackslash}X in the template // 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 columnSpec = headers.joinToString("") { "Y" }
val headerRow = headers.joinToString(" & ") { "\\textbf{$it}" } val headerRow = headers.joinToString(" & ") { "\\textbf{$it}" }
val dataRows = val dataRows =
@@ -166,8 +178,13 @@ class ApplicationFormFormatService(
} }
} }
return buildString { val latexContent = buildString {
appendLine("\\begin{tabularx}{\\textwidth}{$columnSpec}") 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("\\toprule")
appendLine("$headerRow \\\\") appendLine("$headerRow \\\\")
appendLine("\\midrule") appendLine("\\midrule")
@@ -176,7 +193,19 @@ class ApplicationFormFormatService(
} }
appendLine("\\bottomrule") appendLine("\\bottomrule")
appendLine("\\end{tabularx}") 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 { private fun filterVisibleElements(snapshot: ApplicationFormSnapshotDto): ApplicationFormSnapshotDto {

View File

@@ -29,4 +29,5 @@ data class LatexFormElement(
val description: String?, val description: String?,
val value: String, val value: String,
val isTable: Boolean = false, val isTable: Boolean = false,
val isWideTable: Boolean = false,
) )

View File

@@ -18,10 +18,16 @@
\usepackage{tabularx} \usepackage{tabularx}
\usepackage{array} \usepackage{array}
\usepackage{booktabs} \usepackage{booktabs}
\usepackage{pdflscape}
\usepackage{scrlayer-scrpage}
\usepackage{geometry}
% Define column type for auto-wrapping text % Define column type for auto-wrapping text
\newcolumntype{Y}{>{\raggedright\arraybackslash}X} \newcolumntype{Y}{>{\raggedright\arraybackslash}X}
% Page style for landscape pages - no header/footer to maximize table space
\newpagestyle{landscapestyle}{}{}
\hypersetup{ \hypersetup{
colorlinks=true, colorlinks=true,
linkcolor=black, linkcolor=black,
@@ -74,6 +80,21 @@ Dieses Dokument enthält die Details der Betriebsvereinbarung "[[${applicationFo
[/] [/]
[# th:each="element : ${subsection.elements}"] [# 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}]]} \paragraph{[[${element.title}]]}
[# th:if="${element.description}"] [# th:if="${element.description}"]
\textit{\small [[${element.description}]]} \textit{\small [[${element.description}]]}
@@ -95,6 +116,7 @@ Dieses Dokument enthält die Details der Betriebsvereinbarung "[[${applicationFo
[/] [/]
[/] [/]
[/] [/]
[/]
\vspace{3cm} \vspace{3cm}