diff --git a/renderer-pdf/src/main/kotlin/de/pfadfinder/songbook/renderer/pdf/PdfBookRenderer.kt b/renderer-pdf/src/main/kotlin/de/pfadfinder/songbook/renderer/pdf/PdfBookRenderer.kt index 198a7c8..d6fb6e3 100644 --- a/renderer-pdf/src/main/kotlin/de/pfadfinder/songbook/renderer/pdf/PdfBookRenderer.kt +++ b/renderer-pdf/src/main/kotlin/de/pfadfinder/songbook/renderer/pdf/PdfBookRenderer.kt @@ -424,60 +424,68 @@ class PdfBookRenderer : BookRenderer { y -= config.layout.verseSpacing / 0.3528f } - // Render notes on the last page - if (isLastPage && song.notes.isNotEmpty()) { - y -= 4f + // Render footer elements (notes, metadata, references) anchored to the bottom of the page. + // Instead of flowing from the current y position after song content, we compute a fixed + // starting Y at the top of the footer area (bottomMargin + footerReservation) and render + // top-down: notes -> metadata -> references. This ensures footer elements always appear + // at the same vertical position regardless of how much song content is on the page. + if (isLastPage && footerReservation > 0f) { val metaFont = fontMetrics.getBaseFont(config.fonts.metadata) val metaSize = config.fonts.metadata.size - val noteLineHeight = metaSize * 1.5f - for ((idx, note) in song.notes.withIndex()) { - val wrappedLines = wrapText(note, metaFont, metaSize, contentWidth) - for (wrappedLine in wrappedLines) { - cb.beginText() - cb.setFontAndSize(metaFont, metaSize) - cb.setColorFill(Color.GRAY) - cb.setTextMatrix(leftMargin, y - metaSize) - cb.showText(wrappedLine) - cb.endText() - y -= noteLineHeight - } - // Add paragraph spacing between note paragraphs - if (idx < song.notes.size - 1) { - y -= noteLineHeight * 0.3f - } - } - } + // The footer area spans from bottomMargin to bottomMargin + footerReservation. + // Start rendering from the top of this area, flowing downward. + var footerY = bottomMargin + footerReservation - // Render metadata at bottom of song page (if configured) - on the last page only - if (renderMetaAtBottom && isLastPage) { - val metaParts = buildMetadataLines(song, config) - if (metaParts.isNotEmpty()) { - y -= 4f - val metaFont = fontMetrics.getBaseFont(config.fonts.metadata) - val metaSize = config.fonts.metadata.size - for (metaLine in metaParts) { - val wrappedLines = wrapText(metaLine, metaFont, metaSize, contentWidth) + // Render notes (topmost footer element) + if (song.notes.isNotEmpty()) { + footerY -= 4f // gap before notes + val noteLineHeight = metaSize * 1.5f + for ((idx, note) in song.notes.withIndex()) { + val wrappedLines = wrapText(note, metaFont, metaSize, contentWidth) for (wrappedLine in wrappedLines) { cb.beginText() cb.setFontAndSize(metaFont, metaSize) cb.setColorFill(Color.GRAY) - cb.setTextMatrix(leftMargin, y - metaSize) + cb.setTextMatrix(leftMargin, footerY - metaSize) cb.showText(wrappedLine) cb.endText() - y -= metaSize * 1.5f + footerY -= noteLineHeight + } + if (idx < song.notes.size - 1) { + footerY -= noteLineHeight * 0.3f } } } - } - // 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, y, contentWidth - ) + // Render metadata (Worte/Weise) below notes, if configured at bottom + if (renderMetaAtBottom) { + val metaParts = buildMetadataLines(song, config) + if (metaParts.isNotEmpty()) { + footerY -= 4f // gap before metadata + for (metaLine in metaParts) { + val wrappedLines = wrapText(metaLine, metaFont, metaSize, contentWidth) + for (wrappedLine in wrappedLines) { + cb.beginText() + cb.setFontAndSize(metaFont, metaSize) + cb.setColorFill(Color.GRAY) + cb.setTextMatrix(leftMargin, footerY - metaSize) + cb.showText(wrappedLine) + cb.endText() + footerY -= metaSize * 1.5f + } + } + } + } + + // Render reference book footer (bottommost footer element, just above page number) + if (config.referenceBooks.isNotEmpty() && song.references.isNotEmpty()) { + footerY -= 4f // gap before reference footer + renderReferenceFooter( + cb, fontMetrics, config, song, + leftMargin, footerY, contentWidth + ) + } } }