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 {
namespace = "mg.dot.feufaro"
compileSdk = libs.versions.android.compileSdk.get().toInt()

View file

@ -253,16 +253,22 @@ private fun drawNoteWithSubscript(
text: String, x: Float, y: Float
) {
var curX = x
var i = 0
while (i < text.length) {
val str = text[i].toString()
val superscriptOffset = fontSize * 0.35f
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.setFont(font, fontSize)
cs.newLineAtOffset(curX, y)
cs.showText(str.sanitize())
cs.setFont(font, currentSize)
cs.newLineAtOffset(curX, currentY)
cs.showText(str)
cs.endText()
curX += font.getStringWidth(str.sanitize()) / 1000f * fontSize
i++
curX += font.getStringWidth(str) / 1000f * currentSize + 0.5f
}
}
@ -446,11 +452,11 @@ private suspend fun generatePdfToBytes(
val customNbGrid = settings.nbGridOnMeasure
// ── Polices ────────────────────────────────────────────────────────────────
val noteFontBytes = Res.readBytes("files/PTSerif-Regular.ttf")
val lyricFontBytes = Res.readBytes("files/LinLibertine_R.ttf")
val markerItalicBoldFontBytes = Res.readBytes("files/PT Serif Bold Italic.ttf")
val markerBoldFontBytes = Res.readBytes("files/PTSerif-Bold.ttf")
val emmentalerFontBytes = Res.readBytes("files/emmentaler-20.ttf")
val noteFontBytes = Res.readBytes("font/PTSerif-Regular.ttf")
val lyricFontBytes = Res.readBytes("font/LinLibertine_R.ttf")
val markerItalicBoldFontBytes = Res.readBytes("font/PT_Serif_Bold_Italic.ttf")
val markerBoldFontBytes = Res.readBytes("font/PTSerif_Bold.ttf")
val emmentalerFontBytes = Res.readBytes("font/Emmentaler.ttf")
PDDocument().use { doc ->
@ -611,6 +617,15 @@ private suspend fun generatePdfToBytes(
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
if (markerText.isNotBlank() && !markerText.contains("𝄐")) {
val dsRegex = Regex("""D\.?S\.?""")

View file

@ -33,28 +33,28 @@ object HarmonicAnalyzer {
)
private val chordDefinitions = listOf(
ChordDef("maj7", setOf(0, 4, 7, 11), setOf(4, 11), 12),
ChordDef("7", setOf(0, 4, 7, 10), setOf(4, 10), 12),
ChordDef("m7", setOf(0, 3, 7, 10), setOf(3, 10), 12),
ChordDef("dim7", setOf(0, 3, 6, 9), setOf(3, 6, 9), 12),
ChordDef("m7b5", setOf(0, 3, 6, 10), setOf(3, 6, 10), 12),
ChordDef("7sus4", setOf(0, 5, 7, 10), setOf(5, 10), 12),
ChordDef("maj7sus2", setOf(0, 2, 7, 11), setOf(2, 11), 12),
ChordDef("maj7#11", setOf(0, 4, 6, 7), setOf(4, 6), 12),
ChordDef("add9", setOf(0, 2, 4, 7), setOf(2, 4), 11),
ChordDef("madd9", setOf(0, 2, 3, 7), setOf(2, 3), 11),
ChordDef("add11", setOf(0, 4, 5, 7), setOf(4, 5), 11),
ChordDef("maj", setOf(0, 4, 7, 11), setOf(4, 11), 12),
ChordDef("", setOf(0, 4, 7, 10), setOf(4, 10), 12),
ChordDef("ₘ⁷", setOf(0, 3, 7, 10), setOf(3, 10), 12),
ChordDef("dim", setOf(0, 3, 6, 9), setOf(3, 6, 9), 12),
ChordDef("ₘ⁷b⁵", setOf(0, 3, 6, 10), setOf(3, 6, 10), 12),
ChordDef("⁷sus⁴", setOf(0, 5, 7, 10), setOf(5, 10), 12),
ChordDef("maj⁷sus²", setOf(0, 2, 7, 11), setOf(2, 11), 12),
ChordDef("maj⁷#¹¹", setOf(0, 4, 6, 7), setOf(4, 6), 12),
ChordDef("add", setOf(0, 2, 4, 7), setOf(2, 4), 11),
ChordDef("ₘadd⁹", setOf(0, 2, 3, 7), setOf(2, 3), 11),
ChordDef("add¹¹", setOf(0, 4, 5, 7), setOf(4, 5), 11),
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("aug", setOf(0, 4, 8), setOf(4, 8), 8),
ChordDef("sus2", setOf(0, 2, 7), setOf(2), 8),
ChordDef("sus4", setOf(0, 5, 7), setOf(5), 8),
ChordDef("sus²", setOf(0, 2, 7), setOf(2), 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("m", setOf(0, 3), setOf(3), 5),
ChordDef("", setOf(0, 3), setOf(3), 5),
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.unit.*
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.launch
import kotlinx.coroutines.withContext
@ -54,6 +58,7 @@ import mg.dot.feufaro.getPathValidator
import mg.dot.feufaro.ui.rememberFileSaveLauncher
import mg.dot.feufaro.viewmodel.MidiMarkers
import mg.dot.feufaro.viewmodel.SolfaScreenModel
import org.jetbrains.compose.resources.Font
import java.io.File
import kotlin.math.min
import kotlin.math.roundToInt
@ -689,6 +694,11 @@ fun LazyVerticalGridTUO(
val totalSyllables = sharedScreenModel.synchronizedSyllables.value.size
//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 containerWidthDp = gridWidthDp / gridColumnCount
val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
@ -757,6 +767,7 @@ fun LazyVerticalGridTUO(
val lineHeightDp : Dp = with(density) {
lineHeight.toDp()
}
var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value
var fontStyle = FontStyle.Normal
var fontWeight = FontWeight.Normal
val hairPinSymbol = tuo.hasHairPin()
@ -811,11 +822,21 @@ fun LazyVerticalGridTUO(
// problème si template = $QD:,-$QD
tuo.pTemplate.resetCalledMarker()
val text = tuo.pTemplate.markerToString()
if (text.contains(Regex("^([mfp]+|[rR]it.*)$"))) {
fontStyle = FontStyle.Italic
}
if (text.contains(Regex("^\\s*(ppp|pp|p|mp|mf|f|ff|fff)\\s*$"))) {
fontWeight = FontWeight.Bold
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)
val musicForEmmentRegex = Regex("""\b(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""", RegexOption.IGNORE_CASE)
when {
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,
modifier = Modifier
@ -823,10 +844,11 @@ fun LazyVerticalGridTUO(
softWrap = false,
maxLines = 1,
fontStyle = fontStyle,
fontWeight = fontWeight,
style = TextStyle(
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium.copy(
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 ->
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
// 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 ?
@ -427,10 +431,10 @@ private fun drawNoteWithModulation(
cs.beginText()
cs.setTextMatrix(Matrix.getTranslateInstance(currentX, currentY))
cs.setFont(font, currentSize)
cs.showText(part)
cs.showText(sanitizedPart)
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
) {
var curX = x
var i = 0
while (i < text.length) {
val ch = text[i]
val str = ch.toString()
val superscriptOffset = fontSize * 0.35f
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()
when(ch) {
'₁', '₂', '₃', '¹', '²', '³' -> {
cs.setFont(font, fontSize * 0.6f)
} else -> {
cs.setFont(font, fontSize)
}
}
cs.newLineAtOffset(curX, y)
cs.showText(str.sanitize())
cs.setFont(font, currentSize)
cs.newLineAtOffset(curX, currentY)
cs.showText(str)
cs.endText()
curX += font.getStringWidth(str.sanitize()) / 1000f * fontSize
i++
curX += font.getStringWidth(str) / 1000f * currentSize + 0.5f
}
}
@ -647,11 +650,11 @@ private suspend fun generatePdfToBytes(
val customNbGrid = settings.nbGridOnMeasure
// ── Polices ────────────────────────────────────────────────────
val noteFontBytes = Res.readBytes("files/PTSerif-Regular.ttf")
val lyricFontBytes = Res.readBytes("files/LinLibertine_R.ttf")
val markerItalicBoldFontBytes = Res.readBytes("files/PT Serif Bold Italic.ttf")
val markerBoldFontBytes = Res.readBytes("files/PTSerif-Bold.ttf")
val emmentalerFontBytes = Res.readBytes("files/emmentaler-20.ttf")
val noteFontBytes = Res.readBytes("font/PTSerif-Regular.ttf")
val lyricFontBytes = Res.readBytes("font/LinLibertine_R.ttf")
val markerItalicBoldFontBytes = Res.readBytes("font/PT_Serif_Bold_Italic.ttf")
val markerBoldFontBytes = Res.readBytes("font/PTSerif_Bold.ttf")
val emmentalerFontBytes = Res.readBytes("font/Emmentaler.ttf")
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
if (markerText.isNotBlank() && !markerText.contains("𝄐")) {
if (markerText.isNotBlank() && !markerText.contains("𝄐") && !markerText.contains("")) {
val dsRegex = Regex("""D\.?S\.?""")
val dcRegex = Regex("""D\.?C\.?""")
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)
}
else if (markerText.contains(dsRegex) ||