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
|
//lyrics
|
||||||
(1..2).forEach { index ->
|
//(1..2).forEach { index ->
|
||||||
tuo.lyricsAsMultiString(index)
|
tuo.lyricsAsMultiString(1)
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.forEach { line ->
|
.forEach { line ->
|
||||||
val trimmedLine = cleanSyllable(line)
|
val trimmedLine = cleanSyllable(line)
|
||||||
|
|
@ -98,7 +98,7 @@ private fun bestColWidthPt(
|
||||||
maxLyrW = x
|
maxLyrW = x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
val minRequiredByLyrics = maxLyrW * 0.65f
|
val minRequiredByLyrics = maxLyrW * 0.65f
|
||||||
|
|
||||||
|
|
@ -453,7 +453,8 @@ actual fun rememberPdfExportAction(
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
val (pdfBytes, computedFileName) = generatePdfToBytes(
|
val (pdfBytes, computedFileName) = generatePdfToBytes(
|
||||||
gridData, songTitle, measure, stanza,
|
gridData, songTitle, measure, stanza,
|
||||||
nbStanza, songKey, songAut, songComp, songRythm
|
nbStanza, songKey, songAut, songComp, songRythm,
|
||||||
|
settings
|
||||||
)
|
)
|
||||||
val chosenPath = withContext(Dispatchers.Main) {
|
val chosenPath = withContext(Dispatchers.Main) {
|
||||||
fileRepository.pickSavePath(computedFileName, "Exporter en PDF")
|
fileRepository.pickSavePath(computedFileName, "Exporter en PDF")
|
||||||
|
|
@ -520,8 +521,16 @@ private suspend fun generatePdfToBytes(
|
||||||
|
|
||||||
// ── Largeur optimale de colonne ────────────────────────────────────────
|
// ── Largeur optimale de colonne ────────────────────────────────────────
|
||||||
val bestColW = bestColWidthPt(tuoList, customFont, noteFontSize, lyricFont, lyricFontSize)
|
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) {
|
val colCount = if (customNbGrid == 0) {
|
||||||
((usableWidth / bestColW).toInt()).coerceAtLeast(1)
|
val rawColsInt = (usableWidth / bestColW).toInt()
|
||||||
|
val roundedToMultiple = (rawColsInt / multiple) * multiple
|
||||||
|
roundedToMultiple.coerceAtLeast(multiple)
|
||||||
} else {
|
} else {
|
||||||
customNbGrid
|
customNbGrid
|
||||||
}
|
}
|
||||||
|
|
@ -757,7 +766,19 @@ private suspend fun generatePdfToBytes(
|
||||||
nbStanza = nbStanza
|
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 ->
|
rows.forEachIndexed { rowIdx, rowTuos ->
|
||||||
val maxNoteLines = (rowTuos.maxOfOrNull { it.noteAsMultiString().split("\n").size } ?: 1) + 1
|
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 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> =
|
fun singingVoiceIndices(tuo: TimeUnitObject): List<Int> =
|
||||||
tuo.noteAsMultiString().split("\n").mapIndexedNotNull { li, line ->
|
tuo.noteAsMultiString().split("\n").mapIndexedNotNull { li, line ->
|
||||||
|
|
@ -1139,7 +1159,7 @@ private suspend fun generatePdfToBytes(
|
||||||
if (dcIdx > tuoIdx) break
|
if (dcIdx > tuoIdx) break
|
||||||
result.append(dcText).append(" "); dcIndex++
|
result.append(dcText).append(" "); dcIndex++
|
||||||
}
|
}
|
||||||
result.append(")"); inDcBlock = false
|
result.append(") "); inDcBlock = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return listOf(result.toString().trim())
|
return listOf(result.toString().trim())
|
||||||
|
|
|
||||||
|
|
@ -84,34 +84,75 @@ private fun bestColWidthPt(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//lyrics
|
//lyrics
|
||||||
(1..2).forEach { index ->
|
/*(1..2).forEach { index ->*/
|
||||||
tuo.lyricsAsMultiString(index)
|
tuo.lyricsAsMultiString(1)
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.forEach { line ->
|
.forEach { line ->
|
||||||
val trimmedLine = cleanSyllable(line)
|
val trimmedLine = cleanSyllable(line)
|
||||||
val x = measureNoteWidth(font, lyricFontSize, trimmedLine)
|
val x = measureNoteWidth(font, lyricFontSize * 0.85f, trimmedLine)
|
||||||
if (x > maxLyrW) {
|
if (x > maxLyrW) {
|
||||||
maxLyrW = x
|
maxLyrW = x
|
||||||
// println("Lyr: [$trimmedLine] = [$maxLyrW]")
|
// println("Lyr: [$trimmedLine] = [$maxLyrW]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
/*}*/
|
||||||
}
|
}
|
||||||
//La colonne fait la taille de la note + marge,
|
//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)}]")
|
// 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 {
|
private fun measureNoteWidth(font: PDType0Font, fontSize: Float, text: String): Float {
|
||||||
var totalW = 0f
|
if (text.isEmpty()) return 0f
|
||||||
text.forEach { ch ->
|
|
||||||
totalW += try {
|
// Gère le format avec modulation de type ">t>m" ou "[:d>{ta|d}]"
|
||||||
font.getStringWidth(ch.toString().sanitize()) / 1000f * fontSize
|
if (text.contains(">")) {
|
||||||
} catch (e: Exception) { 0f }
|
var totalW = 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
|
||||||
}
|
}
|
||||||
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 */
|
/** Equivalent de makeSpaceBetweenSyllables pour PDFBox */
|
||||||
private fun spacedSyllable(
|
private fun spacedSyllable(
|
||||||
|
|
@ -729,8 +770,18 @@ private suspend fun generatePdfToBytes(
|
||||||
lyricFont,
|
lyricFont,
|
||||||
lyricFontSize
|
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) {
|
val colCount = if (customNbGrid == 0) {
|
||||||
((usableWidth / bestColW).toInt()).coerceAtLeast(1)
|
val rawColsInt = (usableWidth / bestColW).toInt()
|
||||||
|
val roundedToMultiple = (rawColsInt / multiple) * multiple
|
||||||
|
roundedToMultiple.coerceAtLeast(multiple)
|
||||||
} else {
|
} else {
|
||||||
customNbGrid
|
customNbGrid
|
||||||
}
|
}
|
||||||
|
|
@ -738,6 +789,16 @@ private suspend fun generatePdfToBytes(
|
||||||
//println("ici Colc est $colCount")
|
//println("ici Colc est $colCount")
|
||||||
val colWidth = finalColWidth
|
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
|
val maxSylsSize = tuoList.maxOfOrNull { tuo -> // lyrics Alt
|
||||||
(1..nbStanza).maxOfOrNull { sz ->
|
(1..nbStanza).maxOfOrNull { sz ->
|
||||||
|
|
@ -987,14 +1048,28 @@ private suspend fun generatePdfToBytes(
|
||||||
nbStanza = nbStanza
|
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 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
|
// 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().size == 1 &&
|
||||||
allRows.last().first().noteAsMultiString().trim().isEmpty() &&
|
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 ->
|
rows.forEachIndexed { rowIdx, rowTuos ->
|
||||||
// println("Portée n°: $rowIdx: && rs ${rows.size}")
|
// 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)
|
// Point de départ des notes (en tenant compte des marqueurs)
|
||||||
val notesStartY = if (rowHasMarkers) y - markerHeight else y
|
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> {
|
fun singingVoiceIndices(tuo: TimeUnitObject): List<Int> {
|
||||||
return tuo.noteAsMultiString().split("\n").mapIndexedNotNull { li, line ->
|
return tuo.noteAsMultiString().split("\n").mapIndexedNotNull { li, line ->
|
||||||
val clean = line.trim()
|
val clean = line.trim()
|
||||||
|
|
@ -1677,7 +1751,7 @@ private suspend fun generatePdfToBytes(
|
||||||
dcIndex++
|
dcIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(")")
|
result.append(") ")
|
||||||
// println(" 🎯 FERMETURE )")
|
// println(" 🎯 FERMETURE )")
|
||||||
inDcBlock = false
|
inDcBlock = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue