Sync Column count with measure(multiple) on print
This commit is contained in:
parent
6d5f4cc15c
commit
6a0c3e4e0c
2 changed files with 121 additions and 27 deletions
|
|
@ -88,8 +88,8 @@ private fun bestColWidthPt(
|
|||
}
|
||||
}
|
||||
//lyrics
|
||||
(1..2).forEach { index ->
|
||||
tuo.lyricsAsMultiString(index)
|
||||
//(1..2).forEach { index ->
|
||||
tuo.lyricsAsMultiString(1)
|
||||
.split("\n")
|
||||
.forEach { line ->
|
||||
val trimmedLine = cleanSyllable(line)
|
||||
|
|
@ -98,7 +98,7 @@ private fun bestColWidthPt(
|
|||
maxLyrW = x
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
val minRequiredByLyrics = maxLyrW * 0.65f
|
||||
|
||||
|
|
@ -453,7 +453,8 @@ actual fun rememberPdfExportAction(
|
|||
scope.launch(Dispatchers.IO) {
|
||||
val (pdfBytes, computedFileName) = generatePdfToBytes(
|
||||
gridData, songTitle, measure, stanza,
|
||||
nbStanza, songKey, songAut, songComp, songRythm
|
||||
nbStanza, songKey, songAut, songComp, songRythm,
|
||||
settings
|
||||
)
|
||||
val chosenPath = withContext(Dispatchers.Main) {
|
||||
fileRepository.pickSavePath(computedFileName, "Exporter en PDF")
|
||||
|
|
@ -520,8 +521,16 @@ private suspend fun generatePdfToBytes(
|
|||
|
||||
// ── Largeur optimale de colonne ────────────────────────────────────────
|
||||
val bestColW = bestColWidthPt(tuoList, customFont, noteFontSize, lyricFont, lyricFontSize)
|
||||
val numerator = measure.substringBefore("/").trim().toIntOrNull() ?: 4
|
||||
val multiple = when (numerator) {
|
||||
2, 4, 8, 10 -> 4
|
||||
3, 6, 9, 12 -> 3
|
||||
else -> 4
|
||||
}
|
||||
val colCount = if (customNbGrid == 0) {
|
||||
((usableWidth / bestColW).toInt()).coerceAtLeast(1)
|
||||
val rawColsInt = (usableWidth / bestColW).toInt()
|
||||
val roundedToMultiple = (rawColsInt / multiple) * multiple
|
||||
roundedToMultiple.coerceAtLeast(multiple)
|
||||
} else {
|
||||
customNbGrid
|
||||
}
|
||||
|
|
@ -757,7 +766,19 @@ private suspend fun generatePdfToBytes(
|
|||
nbStanza = nbStanza
|
||||
)
|
||||
|
||||
val rows = tuoList.chunked(colCount)
|
||||
val pitchPattern = "\\b(d|di|r|ri|m|mi|f|fi|s|si|l|la|t|ta)\\b".toRegex()
|
||||
fun isRowEmptyOrSeparatorOnly(rowTuos: List<TimeUnitObject>): Boolean {
|
||||
return rowTuos.all { tuo ->
|
||||
val noteContent = tuo.noteAsMultiString()
|
||||
!pitchPattern.containsMatchIn(noteContent)
|
||||
}
|
||||
}
|
||||
val allRows = tuoList.chunked(colCount)
|
||||
val rows = if (allRows.size > 1 && isRowEmptyOrSeparatorOnly(allRows.last())) {
|
||||
allRows.dropLastWhile { isRowEmptyOrSeparatorOnly(it) }
|
||||
} else {
|
||||
allRows
|
||||
}
|
||||
|
||||
rows.forEachIndexed { rowIdx, rowTuos ->
|
||||
val maxNoteLines = (rowTuos.maxOfOrNull { it.noteAsMultiString().split("\n").size } ?: 1) + 1
|
||||
|
|
@ -776,7 +797,6 @@ private suspend fun generatePdfToBytes(
|
|||
|
||||
val notesStartY = if (rowHasMarkers) y - markerHeight else y
|
||||
|
||||
val pitchPattern = "\\b(d|di|r|ri|m|mi|f|fi|s|si|l|la|t|ta)\\b".toRegex()
|
||||
|
||||
fun singingVoiceIndices(tuo: TimeUnitObject): List<Int> =
|
||||
tuo.noteAsMultiString().split("\n").mapIndexedNotNull { li, line ->
|
||||
|
|
@ -1139,7 +1159,7 @@ private suspend fun generatePdfToBytes(
|
|||
if (dcIdx > tuoIdx) break
|
||||
result.append(dcText).append(" "); dcIndex++
|
||||
}
|
||||
result.append(")"); inDcBlock = false
|
||||
result.append(") "); inDcBlock = false
|
||||
}
|
||||
}
|
||||
return listOf(result.toString().trim())
|
||||
|
|
|
|||
|
|
@ -84,34 +84,75 @@ private fun bestColWidthPt(
|
|||
}
|
||||
}
|
||||
//lyrics
|
||||
(1..2).forEach { index ->
|
||||
tuo.lyricsAsMultiString(index)
|
||||
/*(1..2).forEach { index ->*/
|
||||
tuo.lyricsAsMultiString(1)
|
||||
.split("\n")
|
||||
.forEach { line ->
|
||||
val trimmedLine = cleanSyllable(line)
|
||||
val x = measureNoteWidth(font, lyricFontSize, trimmedLine)
|
||||
val x = measureNoteWidth(font, lyricFontSize * 0.85f, trimmedLine)
|
||||
if (x > maxLyrW) {
|
||||
maxLyrW = x
|
||||
// println("Lyr: [$trimmedLine] = [$maxLyrW]")
|
||||
}
|
||||
}
|
||||
}
|
||||
/*}*/
|
||||
}
|
||||
//La colonne fait la taille de la note + marge,
|
||||
val minRequiredByLyrics = maxLyrW * 0.65f
|
||||
val minRequiredByLyrics = maxLyrW * 0.85f
|
||||
// println("La taille est = [${maxOf(maxNoteW + 2f, minRequiredByLyrics)}]")
|
||||
return maxOf(maxNoteW + 2f, minRequiredByLyrics)
|
||||
val finalWidth = maxOf(maxNoteW/* + 2f*/, minRequiredByLyrics)
|
||||
|
||||
// println("┌──────────────────────────────────────────────────────────┐")
|
||||
// println("│ DEBUG: MEASURE COLUMNS & MAX WIDTH │")
|
||||
// println("├──────────────────────────────────────────────────────────┤")
|
||||
// println("│ Note max : \"${maxNoteW.toString()}\" -> Width: $maxNoteW pt (+2pt margin = ${maxNoteW/* + 2f*/} pt)")
|
||||
// println("│ Lyric max : \"${maxLyrW.toString()}\" -> Width: $maxLyrW pt (Required * 0.85 = $minRequiredByLyrics pt)")
|
||||
// println("│ Winner : ${if (maxNoteW + 2f >= minRequiredByLyrics) "NOTE" else "LYRIC"}")
|
||||
// println("│ Best Col Width Retained : $finalWidth pt")
|
||||
// println("└──────────────────────────────────────────────────────────┘")
|
||||
return finalWidth
|
||||
}
|
||||
|
||||
/** Mesure la largeur exacte comme drawNoteWithSubscript */
|
||||
/** Mesure la largeur exacte en reproduisant le comportement de drawNoteWithModulation et drawNoteWithSubscript */
|
||||
private fun measureNoteWidth(font: PDType0Font, fontSize: Float, text: String): Float {
|
||||
if (text.isEmpty()) return 0f
|
||||
|
||||
// Gère le format avec modulation de type ">t>m" ou "[:d>{ta|d}]"
|
||||
if (text.contains(">")) {
|
||||
var totalW = 0f
|
||||
text.forEach { ch ->
|
||||
totalW += try {
|
||||
font.getStringWidth(ch.toString().sanitize()) / 1000f * fontSize
|
||||
} catch (e: Exception) { 0f }
|
||||
val parts = text.split(">")
|
||||
parts.forEachIndexed { index, part ->
|
||||
println("i$index [$part]")
|
||||
if (part.isNotEmpty()) {
|
||||
val sanitizedPart = part.sanitize()
|
||||
// Le premier élément après '>' est l'exposant/modulation (réduit à 0.7f)
|
||||
val isExposant = index == 1
|
||||
val currentSize = if (isExposant) fontSize * 0.5f else fontSize
|
||||
totalW += textWidth(font, currentSize, sanitizedPart) + 0.5f
|
||||
}
|
||||
}
|
||||
return totalW
|
||||
}
|
||||
|
||||
var curX = 0f
|
||||
for (char in text) {
|
||||
val str = char.toString().sanitize()
|
||||
val isSuperscript = char in setOf('¹', '²', '³', '⁴', '⁵')
|
||||
val isSubscript = char in setOf('₁', '₂', '₃', '₄', '₅')
|
||||
val isPunctuation = char in setOf('•', ',')
|
||||
|
||||
val currentSize = if (isSuperscript || isSubscript) fontSize * 0.6f else fontSize
|
||||
val charWidth = try {
|
||||
font.getStringWidth(str) / 1000f * currentSize
|
||||
} catch (e: Exception) { 0f }
|
||||
|
||||
curX += when {
|
||||
isSuperscript || isSubscript -> charWidth * 0.3f
|
||||
isPunctuation -> charWidth
|
||||
else -> charWidth + 0.5f
|
||||
}
|
||||
}
|
||||
return curX
|
||||
}
|
||||
/** Equivalent de makeSpaceBetweenSyllables pour PDFBox */
|
||||
private fun spacedSyllable(
|
||||
|
|
@ -729,8 +770,18 @@ private suspend fun generatePdfToBytes(
|
|||
lyricFont,
|
||||
lyricFontSize
|
||||
)
|
||||
|
||||
val numerator = measure.substringBefore("/").trim().toIntOrNull() ?: 4
|
||||
val multiple = when (numerator) {
|
||||
2, 4, 8, 10 -> 4
|
||||
3, 6, 9, 12 -> 3
|
||||
else -> 4
|
||||
}
|
||||
|
||||
val colCount = if (customNbGrid == 0) {
|
||||
((usableWidth / bestColW).toInt()).coerceAtLeast(1)
|
||||
val rawColsInt = (usableWidth / bestColW).toInt()
|
||||
val roundedToMultiple = (rawColsInt / multiple) * multiple
|
||||
roundedToMultiple.coerceAtLeast(multiple)
|
||||
} else {
|
||||
customNbGrid
|
||||
}
|
||||
|
|
@ -738,6 +789,16 @@ private suspend fun generatePdfToBytes(
|
|||
//println("ici Colc est $colCount")
|
||||
val colWidth = finalColWidth
|
||||
|
||||
// println("┌──────────────────────────────────────────────────────────┐")
|
||||
// println("│ DEBUG: COLUMN COUNT CALCULATION │")
|
||||
// println("├──────────────────────────────────────────────────────────┤")
|
||||
// println("│ Usable Page Width : $usableWidth pt")
|
||||
// println("│ Best Col Width : $bestColW pt")
|
||||
// println("│ Raw Columns Count : ${(usableWidth / bestColW).toInt()}")
|
||||
// println("│ Multiple Used : $multiple (Measure numerator: $numerator)")
|
||||
// println("│ Final Col Count : $colCount")
|
||||
// println("│ Final Col Width : $finalColWidth pt")
|
||||
// println("└──────────────────────────────────────────────────────────┘")
|
||||
|
||||
val maxSylsSize = tuoList.maxOfOrNull { tuo -> // lyrics Alt
|
||||
(1..nbStanza).maxOfOrNull { sz ->
|
||||
|
|
@ -987,14 +1048,28 @@ private suspend fun generatePdfToBytes(
|
|||
nbStanza = nbStanza
|
||||
)
|
||||
|
||||
val pitchPattern = "\\b(d|di|r|ri|m|mi|f|fi|s|si|l|la|t|ta)\\b".toRegex()
|
||||
fun isRowEmptyOrSeparatorOnly(rowTuos: List<TimeUnitObject>): Boolean {
|
||||
return rowTuos.all { tuo ->
|
||||
val noteContent = tuo.noteAsMultiString()
|
||||
!pitchPattern.containsMatchIn(noteContent)
|
||||
}
|
||||
}
|
||||
val allRows = tuoList.chunked(colCount)
|
||||
val rows = if (allRows.size > 1 && isRowEmptyOrSeparatorOnly(allRows.last())) {
|
||||
allRows.dropLastWhile { isRowEmptyOrSeparatorOnly(it) }
|
||||
} else {
|
||||
allRows
|
||||
}
|
||||
|
||||
/*val allRows = tuoList.chunked(colCount)
|
||||
// si dernier portée n'a qu'une séparateur pas de note on dessine sur l'avant dernier
|
||||
val hasTrailingBarlineRow = allRows.size > 1 &&
|
||||
*/val hasTrailingBarlineRow = allRows.size > 1 &&
|
||||
allRows.last().size == 1 &&
|
||||
allRows.last().first().noteAsMultiString().trim().isEmpty() &&
|
||||
allRows.last().first().sep0 in listOf("|", "/")
|
||||
allRows.last().first().sep0 in listOf("|", "/")/*
|
||||
|
||||
val rows = if (hasTrailingBarlineRow) allRows.dropLast(1) else allRows
|
||||
val rows = if (hasTrailingBarlineRow) allRows.dropLast(1) else allRows*/
|
||||
|
||||
rows.forEachIndexed { rowIdx, rowTuos ->
|
||||
// println("Portée n°: $rowIdx: && rs ${rows.size}")
|
||||
|
|
@ -1023,7 +1098,6 @@ private suspend fun generatePdfToBytes(
|
|||
// Point de départ des notes (en tenant compte des marqueurs)
|
||||
val notesStartY = if (rowHasMarkers) y - markerHeight else y
|
||||
|
||||
val pitchPattern = "\\b(d|di|r|ri|m|mi|f|fi|s|si|l|la|t|ta)\\b".toRegex()
|
||||
fun singingVoiceIndices(tuo: TimeUnitObject): List<Int> {
|
||||
return tuo.noteAsMultiString().split("\n").mapIndexedNotNull { li, line ->
|
||||
val clean = line.trim()
|
||||
|
|
@ -1677,7 +1751,7 @@ private suspend fun generatePdfToBytes(
|
|||
dcIndex++
|
||||
}
|
||||
|
||||
result.append(")")
|
||||
result.append(") ")
|
||||
// println(" 🎯 FERMETURE )")
|
||||
inDcBlock = false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue