feat(#35): Handle wide tables better in PDF preview
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user