feat: anchor footer elements to bottom of page (Closes #31) #32
@@ -424,62 +424,70 @@ 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
|
||||
|
||||
// The footer area spans from bottomMargin to bottomMargin + footerReservation.
|
||||
// Start rendering from the top of this area, flowing downward.
|
||||
var footerY = bottomMargin + footerReservation
|
||||
|
||||
// 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 -= noteLineHeight
|
||||
footerY -= noteLineHeight
|
||||
}
|
||||
// Add paragraph spacing between note paragraphs
|
||||
if (idx < song.notes.size - 1) {
|
||||
y -= noteLineHeight * 0.3f
|
||||
footerY -= noteLineHeight * 0.3f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render metadata at bottom of song page (if configured) - on the last page only
|
||||
if (renderMetaAtBottom && isLastPage) {
|
||||
// Render metadata (Worte/Weise) below notes, if configured at bottom
|
||||
if (renderMetaAtBottom) {
|
||||
val metaParts = buildMetadataLines(song, config)
|
||||
if (metaParts.isNotEmpty()) {
|
||||
y -= 4f
|
||||
val metaFont = fontMetrics.getBaseFont(config.fonts.metadata)
|
||||
val metaSize = config.fonts.metadata.size
|
||||
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, y - metaSize)
|
||||
cb.setTextMatrix(leftMargin, footerY - metaSize)
|
||||
cb.showText(wrappedLine)
|
||||
cb.endText()
|
||||
y -= metaSize * 1.5f
|
||||
footerY -= metaSize * 1.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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, y, contentWidth
|
||||
leftMargin, footerY, contentWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build metadata lines based on configured label style.
|
||||
|
||||
Reference in New Issue
Block a user