feat: support custom font files for song titles (Closes #4)
- Improve PdfFontMetrics: use canonical path for cache key, validate font file existence, use absolute paths for BaseFont.createFont - Add font file path resolution in SongbookPipeline (relative to project directory) - Add font file existence validation in Validator.validateConfig - Add end-to-end tests: custom font loading, umlaut rendering, cache deduplication, missing file error - Document custom font file usage in example songbook.yaml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #14.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package de.pfadfinder.songbook.parser
|
||||
|
||||
import de.pfadfinder.songbook.model.BookConfig
|
||||
import de.pfadfinder.songbook.model.FontSpec
|
||||
import de.pfadfinder.songbook.model.Song
|
||||
import java.io.File
|
||||
|
||||
data class ValidationError(val file: String?, val line: Int?, val message: String)
|
||||
|
||||
@@ -50,6 +52,27 @@ object Validator {
|
||||
if (outer <= 0) errors.add(ValidationError(file = null, line = null, message = "Outer margin must be greater than 0"))
|
||||
}
|
||||
|
||||
// Validate font files exist (paths should already be resolved to absolute by the pipeline)
|
||||
validateFontFile(config.fonts.lyrics, "lyrics", errors)
|
||||
validateFontFile(config.fonts.chords, "chords", errors)
|
||||
validateFontFile(config.fonts.title, "title", errors)
|
||||
validateFontFile(config.fonts.metadata, "metadata", errors)
|
||||
validateFontFile(config.fonts.toc, "toc", errors)
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateFontFile(font: FontSpec, fontRole: String, errors: MutableList<ValidationError>) {
|
||||
val fontFile = font.file ?: return
|
||||
val file = File(fontFile)
|
||||
if (!file.exists()) {
|
||||
errors.add(
|
||||
ValidationError(
|
||||
file = null,
|
||||
line = null,
|
||||
message = "Font file for '$fontRole' not found: $fontFile"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,4 +251,21 @@ class ConfigParserTest {
|
||||
val config = ConfigParser.parse(yaml)
|
||||
config.book.title shouldBe "Test"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parse config with custom title font file only`() {
|
||||
val yaml = """
|
||||
book:
|
||||
title: "Fraktur Test"
|
||||
fonts:
|
||||
title: { file: "./fonts/Fraktur.ttf", size: 16 }
|
||||
""".trimIndent()
|
||||
val config = ConfigParser.parse(yaml)
|
||||
config.fonts.title.file shouldBe "./fonts/Fraktur.ttf"
|
||||
config.fonts.title.size shouldBe 16f
|
||||
config.fonts.title.family shouldBe "Helvetica" // default family as fallback
|
||||
// Other fonts should still use defaults
|
||||
config.fonts.lyrics.file.shouldBeNull()
|
||||
config.fonts.lyrics.family shouldBe "Helvetica"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,4 +206,53 @@ class ValidatorTest {
|
||||
errors shouldHaveSize 1
|
||||
errors[0].file shouldContain "myfile.chopro"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing font file produces validation error`() {
|
||||
val config = BookConfig(
|
||||
fonts = FontsConfig(
|
||||
title = FontSpec(file = "/nonexistent/path/FrakturFont.ttf", size = 14f)
|
||||
)
|
||||
)
|
||||
val errors = Validator.validateConfig(config)
|
||||
errors shouldHaveSize 1
|
||||
errors[0].message shouldContain "title"
|
||||
errors[0].message shouldContain "not found"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple missing font files produce multiple errors`() {
|
||||
val config = BookConfig(
|
||||
fonts = FontsConfig(
|
||||
title = FontSpec(file = "/nonexistent/title.ttf", size = 14f),
|
||||
lyrics = FontSpec(file = "/nonexistent/lyrics.ttf", size = 10f)
|
||||
)
|
||||
)
|
||||
val errors = Validator.validateConfig(config)
|
||||
errors shouldHaveSize 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `config with no font files produces no font errors`() {
|
||||
val config = BookConfig() // all default built-in fonts
|
||||
val errors = Validator.validateConfig(config)
|
||||
errors.shouldBeEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `config with existing font file produces no error`() {
|
||||
// Create a temporary file to simulate an existing font file
|
||||
val tempFile = kotlin.io.path.createTempFile(suffix = ".ttf").toFile()
|
||||
try {
|
||||
val config = BookConfig(
|
||||
fonts = FontsConfig(
|
||||
title = FontSpec(file = tempFile.absolutePath, size = 14f)
|
||||
)
|
||||
)
|
||||
val errors = Validator.validateConfig(config)
|
||||
errors.shouldBeEmpty()
|
||||
} finally {
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user