BlockGrouperTest.kt 96 lignes · 4016 octets
package fr.ebii.card2vcf.ocr

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class BlockGrouperTest {

    private fun ligne(
        text: String,
        top: Int,
        height: Int = 30,
        left: Int = 0,
        right: Int = 300,
        confidence: Float = 90f,
    ) = OcrLine(text, OcrBox(left, top, right, top + height), confidence)

    @Test fun listeVideAucunBloc() {
        assertEquals(emptyList(), BlockGrouper.group(emptyList()))
    }

    @Test fun troisPavesSeparesDonnentTroisBlocs() {
        val lines = listOf(
            ligne("Sonia MARTIN", top = 100, height = 40),
            ligne("Directrice commerciale", top = 150),
            ligne("561 allée des Noisetiers", top = 400),
            ligne("69760 Limonest", top = 440),
            ligne("Tél. 04 75 35 12 34", top = 700),
        )
        val blocs = BlockGrouper.group(lines)
        assertEquals(3, blocs.size)
        assertEquals(listOf("Sonia MARTIN", "Directrice commerciale"), blocs[0].lines.map { it.text })
        assertEquals(listOf("561 allée des Noisetiers", "69760 Limonest"), blocs[1].lines.map { it.text })
        assertEquals(listOf("Tél. 04 75 35 12 34"), blocs[2].lines.map { it.text })
    }

    @Test fun lignesAdresseContiguesGroupees() {
        val blocs = BlockGrouper.group(
            listOf(
                ligne("561 allée des Noisetiers", top = 100),
                ligne("69760 Limonest - France", top = 140),
            ),
        )
        assertEquals(1, blocs.size)
        assertEquals("561 allée des Noisetiers\n69760 Limonest - France", blocs[0].text())
    }

    @Test fun deuxColonnesNonFusionneesMemeSiEntrelaceesVerticalement() {
        val gauche1 = ligne("561 allée des Noisetiers", top = 100, left = 0, right = 300)
        val droite1 = ligne("Tél. 04 75 35 12 34", top = 110, left = 600, right = 900)
        val gauche2 = ligne("69760 Limonest", top = 140, left = 0, right = 300)
        val droite2 = ligne("contact@voltea.fr", top = 150, left = 600, right = 900)
        val blocs = BlockGrouper.group(listOf(gauche1, droite1, gauche2, droite2))
        assertEquals(2, blocs.size)
        val textes = blocs.map { b -> b.lines.map { it.text } }
        assertTrue(textes.contains(listOf("561 allée des Noisetiers", "69760 Limonest")), "colonne gauche intacte: $textes")
        assertTrue(textes.contains(listOf("Tél. 04 75 35 12 34", "contact@voltea.fr")), "colonne droite intacte: $textes")
    }

    @Test fun hauteurMedianeRobusteAUneLigneGeante() {
        // Logo géant (h=200) : la médiane reste ~30, donc l'écart de 60 px sous le logo coupe.
        val blocs = BlockGrouper.group(
            listOf(
                ligne("VOLTEA", top = 0, height = 200),
                ligne("Sonia Martin", top = 260),
                ligne("Directrice", top = 300),
            ),
        )
        assertEquals(2, blocs.size)
        assertEquals(listOf("VOLTEA"), blocs[0].lines.map { it.text })
        assertEquals(listOf("Sonia Martin", "Directrice"), blocs[1].lines.map { it.text })
    }

    @Test fun blocsOrdonnesDeHautEnBasPuisDeGaucheADroite() {
        val basGauche = ligne("bas gauche", top = 500, left = 0, right = 200)
        val hautDroite = ligne("haut droite", top = 100, left = 600, right = 800)
        val hautGauche = ligne("haut gauche", top = 100, left = 0, right = 200)
        val blocs = BlockGrouper.group(listOf(basGauche, hautDroite, hautGauche))
        assertEquals(
            listOf("haut gauche", "haut droite", "bas gauche"),
            blocs.map { it.text() },
        )
    }

    @Test fun proprietesDeBloc() {
        val bloc = OcrBlock(
            listOf(
                OcrLine("a", OcrBox(10, 100, 200, 130), 80f),
                OcrLine("b", OcrBox(20, 140, 300, 170), 60f),
            ),
        )
        assertEquals(OcrBox(10, 100, 300, 170), bloc.box())
        assertEquals(70f, bloc.avgConfidence())
        assertEquals("a\nb", bloc.text())
    }
}