fix: position reference footer below content to prevent overlap (Closes #25)

The reference footer was anchored to a fixed position near the bottom
margin, causing it to overlap with notes text on songs that had both
notes and references (e.g., "Die Gedanken sind frei").

Changed renderReferenceFooter to use flow-based positioning from the
current y-position after notes/metadata, ensuring the separator line,
abbreviation headers, and page numbers always render below the
preceding content without overlap.

Also updated calculateFooterReservation and MeasurementEngine to match
the simplified layout calculation, and added toc.highlight_column to
the example songbook.yaml to demonstrate column highlighting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shahondin1624
2026-03-17 14:59:20 +01:00
parent a251fac053
commit 6d67c051f0
3 changed files with 28 additions and 28 deletions

View File

@@ -91,11 +91,11 @@ class MeasurementEngine(
}
}
// Reference book footer: reserve space for abbreviation row + page number row + separator line
// Reference book footer: gap + separator line + abbreviation row + page number row
if (config.referenceBooks.isNotEmpty() && song.references.isNotEmpty()) {
val metaLineHeight = fontMetrics.measureLineHeight(config.fonts.metadata, config.fonts.metadata.size)
heightMm += 4f * 0.3528f // gap before footer (4pt converted to mm)
heightMm += metaLineHeight * 1.4f * 2 // two rows (headers + numbers)
heightMm += metaLineHeight * 0.5f // separator line gap
}
val pageCount = if (heightMm <= contentHeightMm) 1 else 2

View File

@@ -240,12 +240,11 @@ class PdfBookRenderer : BookRenderer {
}
}
// Reference footer
// Reference footer: gap + separator line + abbreviation row + page number row
if (config.referenceBooks.isNotEmpty() && song.references.isNotEmpty()) {
val lineHeight = metaSize * 1.4f
reserved += 4f // gap before footer
reserved += lineHeight * 2 // two rows (headers + numbers)
reserved += lineHeight * 1.5f // separator line gap
reserved += lineHeight * 0.5f // bottom gap
}
return reserved
@@ -474,10 +473,10 @@ class PdfBookRenderer : BookRenderer {
// Render reference book footer on the last page of the song
if (config.referenceBooks.isNotEmpty() && song.references.isNotEmpty() && isLastPage) {
y -= 4f // gap before reference footer
renderReferenceFooter(
cb, fontMetrics, config, song,
leftMargin, contentWidth,
bottomMargin
leftMargin, y, contentWidth
)
}
}
@@ -512,8 +511,9 @@ class PdfBookRenderer : BookRenderer {
}
/**
* Renders reference book abbreviations and page numbers as a footer row
* at the bottom of the song page, above the page number.
* Renders reference book abbreviations and page numbers as a footer
* on the song page, positioned below the current content at [topY].
* Layout (top to bottom): separator line, abbreviation row, page number row.
*/
private fun renderReferenceFooter(
cb: PdfContentByte,
@@ -521,40 +521,44 @@ class PdfBookRenderer : BookRenderer {
config: BookConfig,
song: Song,
leftMargin: Float,
contentWidth: Float,
bottomMargin: Float
topY: Float,
contentWidth: Float
) {
val metaFont = fontMetrics.getBaseFont(config.fonts.metadata)
val metaSize = config.fonts.metadata.size
val lineHeight = metaSize * 1.4f
// Position: just above the page number area
val footerY = bottomMargin + lineHeight * 0.5f
// Map book IDs to abbreviations
val refAbbreviations = config.referenceBooks.associate { it.id to it.abbreviation }
val books = config.referenceBooks
// Calculate column widths: evenly distribute across content width
val colWidth = contentWidth / books.size
// Row 1: Abbreviation headers
// Draw a thin separator line at the top of the footer
val lineY = topY
cb.setLineWidth(0.3f)
cb.setColorStroke(Color.LIGHT_GRAY)
cb.moveTo(leftMargin, lineY)
cb.lineTo(leftMargin + contentWidth, lineY)
cb.stroke()
// Row 1: Abbreviation headers (below the separator line)
val abbrY = lineY - lineHeight
for ((i, book) in books.withIndex()) {
val x = leftMargin + i * colWidth
val abbr = book.abbreviation
val textWidth = metaFont.getWidthPoint(abbr, metaSize)
// Center text in column
val textX = x + (colWidth - textWidth) / 2
cb.beginText()
cb.setFontAndSize(metaFont, metaSize)
cb.setColorFill(Color.DARK_GRAY)
cb.setTextMatrix(textX, footerY + lineHeight)
cb.setTextMatrix(textX, abbrY)
cb.showText(abbr)
cb.endText()
}
// Row 2: Page numbers
// Row 2: Page numbers (below abbreviation headers)
val numY = abbrY - lineHeight
for ((i, book) in books.withIndex()) {
val x = leftMargin + i * colWidth
val pageNum = song.references[book.id]
@@ -566,18 +570,11 @@ class PdfBookRenderer : BookRenderer {
cb.beginText()
cb.setFontAndSize(metaFont, metaSize)
cb.setColorFill(Color.DARK_GRAY)
cb.setTextMatrix(textX, footerY)
cb.setTextMatrix(textX, numY)
cb.showText(pageText)
cb.endText()
}
}
// Draw a thin line above the footer
cb.setLineWidth(0.3f)
cb.setColorStroke(Color.LIGHT_GRAY)
cb.moveTo(leftMargin, footerY + lineHeight * 1.5f)
cb.lineTo(leftMargin + contentWidth, footerY + lineHeight * 1.5f)
cb.stroke()
}
private fun renderForewordPage(

View File

@@ -37,6 +37,9 @@ reference_books:
name: "Pfadfinderliederbuch"
abbreviation: "PfLB"
toc:
highlight_column: "Seite"
output:
directory: "./output"
filename: "liederbuch.pdf"