feat: use Worte/Weise labels and render metadata at page bottom (Closes #5)
Add metadata_labels ("abbreviated"/"german") and metadata_position
("top"/"bottom") options to LayoutConfig. German labels use "Worte:" and
"Weise:" instead of "T:" and "M:", with "Worte und Weise:" when lyricist
and composer are the same person. Metadata at bottom position renders
after notes with word-wrapping. MeasurementEngine accounts for two-line
metadata in German label mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,8 @@ class PdfBookRenderer : BookRenderer {
|
||||
) {
|
||||
var y = contentTop
|
||||
|
||||
val renderMetaAtBottom = config.layout.metadataPosition == "bottom"
|
||||
|
||||
if (pageIndex == 0) {
|
||||
// Render title
|
||||
val titleFont = fontMetrics.getBaseFont(config.fonts.title)
|
||||
@@ -116,20 +118,22 @@ class PdfBookRenderer : BookRenderer {
|
||||
cb.endText()
|
||||
y -= titleSize * 1.5f
|
||||
|
||||
// Render metadata line (composer/lyricist)
|
||||
val metaParts = mutableListOf<String>()
|
||||
song.composer?.let { metaParts.add("M: $it") }
|
||||
song.lyricist?.let { metaParts.add("T: $it") }
|
||||
if (metaParts.isNotEmpty()) {
|
||||
val metaFont = fontMetrics.getBaseFont(config.fonts.metadata)
|
||||
val metaSize = config.fonts.metadata.size
|
||||
cb.beginText()
|
||||
cb.setFontAndSize(metaFont, metaSize)
|
||||
cb.setColorFill(Color.GRAY)
|
||||
cb.setTextMatrix(leftMargin, y - metaSize)
|
||||
cb.showText(metaParts.joinToString(" / "))
|
||||
cb.endText()
|
||||
y -= metaSize * 1.8f
|
||||
// Render metadata line (composer/lyricist) - at top position only
|
||||
if (!renderMetaAtBottom) {
|
||||
val metaParts = buildMetadataLines(song, config)
|
||||
if (metaParts.isNotEmpty()) {
|
||||
val metaFont = fontMetrics.getBaseFont(config.fonts.metadata)
|
||||
val metaSize = config.fonts.metadata.size
|
||||
for (metaLine in metaParts) {
|
||||
cb.beginText()
|
||||
cb.setFontAndSize(metaFont, metaSize)
|
||||
cb.setColorFill(Color.GRAY)
|
||||
cb.setTextMatrix(leftMargin, y - metaSize)
|
||||
cb.showText(metaLine)
|
||||
cb.endText()
|
||||
y -= metaSize * 1.8f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render key and capo
|
||||
@@ -242,6 +246,28 @@ class PdfBookRenderer : BookRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Render metadata at bottom of song page (if configured)
|
||||
if (renderMetaAtBottom && pageIndex == 0) {
|
||||
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)
|
||||
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 -= metaSize * 1.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render reference book footer on the last page of the song
|
||||
if (config.referenceBooks.isNotEmpty() && song.references.isNotEmpty()) {
|
||||
val isLastPage = (pageIndex == 0) // For now, all content renders on page 0
|
||||
@@ -255,6 +281,35 @@ class PdfBookRenderer : BookRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build metadata lines based on configured label style.
|
||||
* Returns a list of lines to render (may be empty).
|
||||
*/
|
||||
private fun buildMetadataLines(song: Song, config: BookConfig): List<String> {
|
||||
val useGerman = config.layout.metadataLabels == "german"
|
||||
val lines = mutableListOf<String>()
|
||||
|
||||
if (useGerman) {
|
||||
// German labels: "Worte und Weise:" when same person, otherwise separate
|
||||
if (song.lyricist != null && song.composer != null && song.lyricist == song.composer) {
|
||||
lines.add("Worte und Weise: ${song.lyricist}")
|
||||
} else {
|
||||
song.lyricist?.let { lines.add("Worte: $it") }
|
||||
song.composer?.let { lines.add("Weise: $it") }
|
||||
}
|
||||
} else {
|
||||
// Abbreviated labels on a single line
|
||||
val parts = mutableListOf<String>()
|
||||
song.composer?.let { parts.add("M: $it") }
|
||||
song.lyricist?.let { parts.add("T: $it") }
|
||||
if (parts.isNotEmpty()) {
|
||||
lines.add(parts.joinToString(" / "))
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders reference book abbreviations and page numbers as a footer row
|
||||
* at the bottom of the song page, above the page number.
|
||||
|
||||
Reference in New Issue
Block a user