Use musical fonts for specific markers

This commit is contained in:
Hasinjato 2026-07-10 15:19:32 +03:00
parent 9550ab0e3c
commit 90c7b40d84
10 changed files with 114 additions and 61 deletions

View file

@ -102,6 +102,9 @@ kotlin {
} }
} }
compose.resources {
publicResClass = true
}
android { android {
namespace = "mg.dot.feufaro" namespace = "mg.dot.feufaro"
compileSdk = libs.versions.android.compileSdk.get().toInt() compileSdk = libs.versions.android.compileSdk.get().toInt()

View file

@ -253,16 +253,22 @@ private fun drawNoteWithSubscript(
text: String, x: Float, y: Float text: String, x: Float, y: Float
) { ) {
var curX = x var curX = x
var i = 0 val superscriptOffset = fontSize * 0.35f
while (i < text.length) {
val str = text[i].toString() for (char in text) {
val str = char.toString().sanitize()
val isSuperscript = char in setOf('¹', '²', '³')
val isSubscript = char in setOf('₁', '₂', '₃')
val currentSize = if (isSuperscript || isSubscript) fontSize * 0.6f else fontSize
val currentY = if (isSuperscript) y + superscriptOffset else y
cs.beginText() cs.beginText()
cs.setFont(font, fontSize) cs.setFont(font, currentSize)
cs.newLineAtOffset(curX, y) cs.newLineAtOffset(curX, currentY)
cs.showText(str.sanitize()) cs.showText(str)
cs.endText() cs.endText()
curX += font.getStringWidth(str.sanitize()) / 1000f * fontSize curX += font.getStringWidth(str) / 1000f * currentSize + 0.5f
i++
} }
} }
@ -446,11 +452,11 @@ private suspend fun generatePdfToBytes(
val customNbGrid = settings.nbGridOnMeasure val customNbGrid = settings.nbGridOnMeasure
// ── Polices ──────────────────────────────────────────────────────────────── // ── Polices ────────────────────────────────────────────────────────────────
val noteFontBytes = Res.readBytes("files/PTSerif-Regular.ttf") val noteFontBytes = Res.readBytes("font/PTSerif-Regular.ttf")
val lyricFontBytes = Res.readBytes("files/LinLibertine_R.ttf") val lyricFontBytes = Res.readBytes("font/LinLibertine_R.ttf")
val markerItalicBoldFontBytes = Res.readBytes("files/PT Serif Bold Italic.ttf") val markerItalicBoldFontBytes = Res.readBytes("font/PT_Serif_Bold_Italic.ttf")
val markerBoldFontBytes = Res.readBytes("files/PTSerif-Bold.ttf") val markerBoldFontBytes = Res.readBytes("font/PTSerif_Bold.ttf")
val emmentalerFontBytes = Res.readBytes("files/emmentaler-20.ttf") val emmentalerFontBytes = Res.readBytes("font/Emmentaler.ttf")
PDDocument().use { doc -> PDDocument().use { doc ->
@ -611,6 +617,15 @@ private suspend fun generatePdfToBytes(
drawFermata(cs, colX + colWidth / 2 - 5f, markerY - 2f, 0.3f) drawFermata(cs, colX + colWidth / 2 - 5f, markerY - 2f, 0.3f)
} }
if (markerText.contains("")) {
val textPart = markerText.replace("", "BPM")
cs.beginText()
cs.setFont(italicBoldFont, fontSize)
cs.newLineAtOffset(colX, markerY)
cs.setFont(boldFont, fontSize-3f)
cs.showText(textPart)
cs.endText()
}
// Marqueur texte // Marqueur texte
if (markerText.isNotBlank() && !markerText.contains("𝄐")) { if (markerText.isNotBlank() && !markerText.contains("𝄐")) {
val dsRegex = Regex("""D\.?S\.?""") val dsRegex = Regex("""D\.?S\.?""")

View file

@ -33,28 +33,28 @@ object HarmonicAnalyzer {
) )
private val chordDefinitions = listOf( private val chordDefinitions = listOf(
ChordDef("maj7", setOf(0, 4, 7, 11), setOf(4, 11), 12), ChordDef("maj", setOf(0, 4, 7, 11), setOf(4, 11), 12),
ChordDef("7", setOf(0, 4, 7, 10), setOf(4, 10), 12), ChordDef("", setOf(0, 4, 7, 10), setOf(4, 10), 12),
ChordDef("m7", setOf(0, 3, 7, 10), setOf(3, 10), 12), ChordDef("ₘ⁷", setOf(0, 3, 7, 10), setOf(3, 10), 12),
ChordDef("dim7", setOf(0, 3, 6, 9), setOf(3, 6, 9), 12), ChordDef("dim", setOf(0, 3, 6, 9), setOf(3, 6, 9), 12),
ChordDef("m7b5", setOf(0, 3, 6, 10), setOf(3, 6, 10), 12), ChordDef("ₘ⁷b⁵", setOf(0, 3, 6, 10), setOf(3, 6, 10), 12),
ChordDef("7sus4", setOf(0, 5, 7, 10), setOf(5, 10), 12), ChordDef("⁷sus⁴", setOf(0, 5, 7, 10), setOf(5, 10), 12),
ChordDef("maj7sus2", setOf(0, 2, 7, 11), setOf(2, 11), 12), ChordDef("maj⁷sus²", setOf(0, 2, 7, 11), setOf(2, 11), 12),
ChordDef("maj7#11", setOf(0, 4, 6, 7), setOf(4, 6), 12), ChordDef("maj⁷#¹¹", setOf(0, 4, 6, 7), setOf(4, 6), 12),
ChordDef("add9", setOf(0, 2, 4, 7), setOf(2, 4), 11), ChordDef("add", setOf(0, 2, 4, 7), setOf(2, 4), 11),
ChordDef("madd9", setOf(0, 2, 3, 7), setOf(2, 3), 11), ChordDef("ₘadd⁹", setOf(0, 2, 3, 7), setOf(2, 3), 11),
ChordDef("add11", setOf(0, 4, 5, 7), setOf(4, 5), 11), ChordDef("add¹¹", setOf(0, 4, 5, 7), setOf(4, 5), 11),
ChordDef("", setOf(0, 4, 7), setOf(4), 8), ChordDef("", setOf(0, 4, 7), setOf(4), 8),
ChordDef("m", setOf(0, 3, 7), setOf(3), 8), ChordDef("", setOf(0, 3, 7), setOf(3), 8),
ChordDef("dim", setOf(0, 3, 6), setOf(3, 6), 8), ChordDef("dim", setOf(0, 3, 6), setOf(3, 6), 8),
ChordDef("aug", setOf(0, 4, 8), setOf(4, 8), 8), ChordDef("aug", setOf(0, 4, 8), setOf(4, 8), 8),
ChordDef("sus2", setOf(0, 2, 7), setOf(2), 8), ChordDef("sus²", setOf(0, 2, 7), setOf(2), 8),
ChordDef("sus4", setOf(0, 5, 7), setOf(5), 8), ChordDef("sus", setOf(0, 5, 7), setOf(5), 8),
ChordDef("5", setOf(0, 7), setOf(7), 5), ChordDef("", setOf(0, 7), setOf(7), 5),
ChordDef("", setOf(0, 4), setOf(4), 5), ChordDef("", setOf(0, 4), setOf(4), 5),
ChordDef("m", setOf(0, 3), setOf(3), 5), ChordDef("", setOf(0, 3), setOf(3), 5),
ChordDef("", setOf(0), setOf(), 3) ChordDef("", setOf(0), setOf(), 3)
) )

View file

@ -43,6 +43,10 @@ import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import feufaro.composeapp.generated.resources.Emmentaler
import feufaro.composeapp.generated.resources.PTSerif_Bold
import feufaro.composeapp.generated.resources.PT_Serif_Bold_Italic
import feufaro.composeapp.generated.resources.Res
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -54,6 +58,7 @@ import mg.dot.feufaro.getPathValidator
import mg.dot.feufaro.ui.rememberFileSaveLauncher import mg.dot.feufaro.ui.rememberFileSaveLauncher
import mg.dot.feufaro.viewmodel.MidiMarkers import mg.dot.feufaro.viewmodel.MidiMarkers
import mg.dot.feufaro.viewmodel.SolfaScreenModel import mg.dot.feufaro.viewmodel.SolfaScreenModel
import org.jetbrains.compose.resources.Font
import java.io.File import java.io.File
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -689,6 +694,11 @@ fun LazyVerticalGridTUO(
val totalSyllables = sharedScreenModel.synchronizedSyllables.value.size val totalSyllables = sharedScreenModel.synchronizedSyllables.value.size
//println("Sync complète effectuée SUR stz: ${sharedScreenModel.stanza.value}! Total colonnes : $totalSyllables") //println("Sync complète effectuée SUR stz: ${sharedScreenModel.stanza.value}! Total colonnes : $totalSyllables")
} }
var emmentaler = FontFamily(Font(Res.font.Emmentaler))
var ptSerifBoldItalic = FontFamily(Font(Res.font.PT_Serif_Bold_Italic))
var ptSerif = FontFamily(Font(Res.font.PTSerif_Bold))
var markerFontFamily: FontFamily = FontFamily.Default
val textMeasurer = rememberTextMeasurer() val textMeasurer = rememberTextMeasurer()
val containerWidthDp = gridWidthDp / gridColumnCount val containerWidthDp = gridWidthDp / gridColumnCount
val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+") val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
@ -757,6 +767,7 @@ fun LazyVerticalGridTUO(
val lineHeightDp : Dp = with(density) { val lineHeightDp : Dp = with(density) {
lineHeight.toDp() lineHeight.toDp()
} }
var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value
var fontStyle = FontStyle.Normal var fontStyle = FontStyle.Normal
var fontWeight = FontWeight.Normal var fontWeight = FontWeight.Normal
val hairPinSymbol = tuo.hasHairPin() val hairPinSymbol = tuo.hasHairPin()
@ -811,11 +822,21 @@ fun LazyVerticalGridTUO(
// problème si template = $QD:,-$QD // problème si template = $QD:,-$QD
tuo.pTemplate.resetCalledMarker() tuo.pTemplate.resetCalledMarker()
val text = tuo.pTemplate.markerToString() val text = tuo.pTemplate.markerToString()
if (text.contains(Regex("^([mfp]+|[rR]it.*)$"))) { val musicForPtSerifBIRegex = Regex("""^(Largh\.|Grave|Largo|Lento|Adagio|And\.|Andantino|Mod\.|Moderato|Alleg\.|All\.|Viv\.|Vivacissimo|Presto|Prestiss\.|accel\.|rit\.|rall\.|riten\.|string\.|allarg\.|a tempo|Tempo I|rubato|meno mosso|più mosso|cres\.|<|decresc\.|dim\.|>|rfz|fp|pf|sub\.p|sub\.f)""", RegexOption.IGNORE_CASE)
fontStyle = FontStyle.Italic val musicForEmmentRegex = Regex("""\b(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""", RegexOption.IGNORE_CASE)
}
if (text.contains(Regex("^\\s*(ppp|pp|p|mp|mf|f|ff|fff)\\s*$"))) { when {
fontWeight = FontWeight.Bold musicForPtSerifBIRegex.containsMatchIn(text) -> {
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value
markerFontFamily = ptSerifBoldItalic
}
musicForEmmentRegex.containsMatchIn(text) -> {
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value+11
markerFontFamily = emmentaler
}
else -> {
markerFontFamily = ptSerif
}
} }
Text(text = text, Text(text = text,
modifier = Modifier modifier = Modifier
@ -823,10 +844,11 @@ fun LazyVerticalGridTUO(
softWrap = false, softWrap = false,
maxLines = 1, maxLines = 1,
fontStyle = fontStyle, fontStyle = fontStyle,
fontWeight = fontWeight, fontWeight = FontWeight.Bold,
style = TextStyle( style = MaterialTheme.typography.titleMedium.copy(
color = MaterialTheme.colorScheme.onSecondary, color = MaterialTheme.colorScheme.onSecondary,
fontSize = MaterialTheme.typography.titleMedium.fontSize fontSize = markerFontSize.sp,
fontFamily = markerFontFamily
) )
) )
} }

View file

@ -414,6 +414,10 @@ private fun drawNoteWithModulation(
parts.forEachIndexed { index, part -> parts.forEachIndexed { index, part ->
if (part.isEmpty()) return@forEachIndexed if (part.isEmpty()) return@forEachIndexed
val sanitizedPart = part
.replace('\u2015', '-')
.replace('\u2014', '-')
.replace('\u2013', '-')
// RÈGLE : Si c'est avant le dernier segment, c'est un exposant // RÈGLE : Si c'est avant le dernier segment, c'est un exposant
// Dans ">t>m", 't' est à l'index 1 (exposant), 'm' est à l'index 2 (normal) // Dans ">t>m", 't' est à l'index 1 (exposant), 'm' est à l'index 2 (normal)
// ATTENTION : selon votre logique, si c'est ">t>m", t=exposant, m=normal ? // ATTENTION : selon votre logique, si c'est ">t>m", t=exposant, m=normal ?
@ -427,10 +431,10 @@ private fun drawNoteWithModulation(
cs.beginText() cs.beginText()
cs.setTextMatrix(Matrix.getTranslateInstance(currentX, currentY)) cs.setTextMatrix(Matrix.getTranslateInstance(currentX, currentY))
cs.setFont(font, currentSize) cs.setFont(font, currentSize)
cs.showText(part) cs.showText(sanitizedPart)
cs.endText() cs.endText()
currentX += textWidth(font, currentSize, part) + 0.5f currentX += textWidth(font, currentSize, sanitizedPart) + 0.5f
} }
} }
@ -443,23 +447,22 @@ private fun drawNoteWithSubscript(
y: Float y: Float
) { ) {
var curX = x var curX = x
var i = 0 val superscriptOffset = fontSize * 0.35f
while (i < text.length) {
val ch = text[i] for (char in text) {
val str = ch.toString() val str = char.toString().sanitize()
val isSuperscript = char in setOf('¹', '²', '³')
val isSubscript = char in setOf('₁', '₂', '₃')
val currentSize = if (isSuperscript || isSubscript) fontSize * 0.6f else fontSize
val currentY = if (isSuperscript) y + superscriptOffset else y
cs.beginText() cs.beginText()
when(ch) { cs.setFont(font, currentSize)
'₁', '₂', '₃', '¹', '²', '³' -> { cs.newLineAtOffset(curX, currentY)
cs.setFont(font, fontSize * 0.6f) cs.showText(str)
} else -> {
cs.setFont(font, fontSize)
}
}
cs.newLineAtOffset(curX, y)
cs.showText(str.sanitize())
cs.endText() cs.endText()
curX += font.getStringWidth(str.sanitize()) / 1000f * fontSize curX += font.getStringWidth(str) / 1000f * currentSize + 0.5f
i++
} }
} }
@ -647,11 +650,11 @@ private suspend fun generatePdfToBytes(
val customNbGrid = settings.nbGridOnMeasure val customNbGrid = settings.nbGridOnMeasure
// ── Polices ──────────────────────────────────────────────────── // ── Polices ────────────────────────────────────────────────────
val noteFontBytes = Res.readBytes("files/PTSerif-Regular.ttf") val noteFontBytes = Res.readBytes("font/PTSerif-Regular.ttf")
val lyricFontBytes = Res.readBytes("files/LinLibertine_R.ttf") val lyricFontBytes = Res.readBytes("font/LinLibertine_R.ttf")
val markerItalicBoldFontBytes = Res.readBytes("files/PT Serif Bold Italic.ttf") val markerItalicBoldFontBytes = Res.readBytes("font/PT_Serif_Bold_Italic.ttf")
val markerBoldFontBytes = Res.readBytes("files/PTSerif-Bold.ttf") val markerBoldFontBytes = Res.readBytes("font/PTSerif_Bold.ttf")
val emmentalerFontBytes = Res.readBytes("files/emmentaler-20.ttf") val emmentalerFontBytes = Res.readBytes("font/Emmentaler.ttf")
PDDocument().use { doc -> PDDocument().use { doc ->
@ -831,13 +834,23 @@ private suspend fun generatePdfToBytes(
) )
} }
if (markerText.contains("")) {
val textPart = markerText.replace("", "BPM")
cs.beginText()
cs.setFont(italicBoldFont, fontSize)
cs.newLineAtOffset(colX, markerY)
cs.setFont(boldFont, fontSize-3f)
cs.showText(textPart)
cs.endText()
}
// marqueur // marqueur
if (markerText.isNotBlank() && !markerText.contains("𝄐")) { if (markerText.isNotBlank() && !markerText.contains("𝄐") && !markerText.contains("")) {
val dsRegex = Regex("""D\.?S\.?""") val dsRegex = Regex("""D\.?S\.?""")
val dcRegex = Regex("""D\.?C\.?""") val dcRegex = Regex("""D\.?C\.?""")
cs.beginText() cs.beginText()
if(markerText.matches(Regex("^\\s*(ppp|pp|p|mp|mf|f|ff|fff)\\s*$"))) { if(markerText.matches(Regex("^\\s*(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\\s*$"))) {
cs.setFont(emmentalerFont, fontSize+10f) cs.setFont(emmentalerFont, fontSize+10f)
} }
else if (markerText.contains(dsRegex) || else if (markerText.contains(dsRegex) ||