Nucleus
PDF reader

Premiers pas

Chargez des octets PDF, réagissez à l'état de chargement et activez la sélection de texte, les liens, les vignettes et le zoom.

Dans ce tutoriel, vous ouvrez un PDF depuis un sélecteur de fichiers, réagissez à son état de chargement et activez la sélection de texte, les liens, une barre latérale de vignettes et le zoom. Chaque extrait se place dans un source set commonMain de Compose Multiplatform.

Avant de commencer

Ajoutez la dépendance décrite dans l'aperçu du lecteur PDF :

build.gradle.kts
commonMain.dependencies {
    implementation("dev.nucleusframework:pdfium:152.0.7947.0")
}

Charger les octets du PDF

PdfReaderState.open prend un ByteArray — la façon dont vous l'obtenez vous appartient. Pour choisir un fichier avec une boîte de dialogue native, utilisez FileKit :

@Composable
fun PdfPicker() {
    val reader = rememberPdfReaderState()
    val scope = rememberCoroutineScope()
    val picker = rememberFilePickerLauncher(
        type = FileKitType.File(extensions = listOf("pdf")),
    ) { file ->
        if (file != null) scope.launch { reader.open(file.readBytes()) }
    }
    Column(Modifier.fillMaxSize()) {
        Button(onClick = { picker.launch() }) { Text("Ouvrir un PDF…") }
        if (reader.pageCount > 0) {
            PdfReader(state = reader, modifier = Modifier.fillMaxSize())
        }
    }
}

Pour charger depuis une URL, passez les octets depuis n'importe quel client HTTP :

val client = remember { HttpClient() }
LaunchedEffect(url) { reader.open(client.get(url).readRawBytes()) }

Réagir à l'état de chargement et aux erreurs

PdfReaderState expose un état snapshot lisible dans n'importe quel composable — isLoading, error, metadata et pageCount :

Box(Modifier.fillMaxSize()) {
    when {
        reader.isLoading -> CircularProgressIndicator(Modifier.align(Alignment.Center))

        reader.error is PdfError.PasswordRequired -> PasswordPrompt { password ->
            scope.launch { reader.open(bytes, password) }
        }
        reader.error is PdfError.InvalidFormat -> Text("PDF invalide")
        reader.error != null -> Text("Ouverture impossible : ${reader.error?.message}")

        reader.pageCount > 0 -> {
            Column {
                Text(reader.metadata.title ?: "Sans titre")
                PdfReader(state = reader, modifier = Modifier.weight(1f))
            }
        }
    }
}

Activer la sélection de texte

Passez selectableText = true sur PdfPage. Glissez sur desktop, appui long sur mobile, et copiez avec Ctrl/Cmd+C :

PdfPage(
    state = reader,
    pageIndex = pageIndex,
    modifier = Modifier.fillMaxWidth(),
    selectableText = true,
)

Le hit-testing utilise les boîtes par caractère de PDFium (FPDFText_GetCharBox), donc la sélection suit les glyphes rendus plutôt que les métriques de police de Compose.

Gérer les liens

Les liens sont activés par défaut sur PdfPage et PdfReader. Les annotations de lien et les URL ou adresses e-mail détectées dans le texte de la page deviennent des régions cliquables. Les URI externes s'ouvrent via LocalUriHandler ; dans PdfReader, les liens GoTo internes défilent jusqu'à la page cible.

Interceptez les clics avec onLinkClick — renvoyez true pour consommer le clic et sauter le traitement par défaut :

PdfReader(
    state = reader,
    onLinkClick = { link ->
        if (link.uri?.startsWith("mailto:") == true) {
            openCustomComposer(link.uri)
            true
        } else {
            false
        }
    },
)

Ajouter une barre latérale de vignettes

PdfThumbnail rend en RenderQuality.PREVIEW et garde son propre LRU, si bien que faire défiler une longue bande n'évince jamais les bitmaps pleine qualité du lecteur :

Row(Modifier.fillMaxSize()) {
    LazyColumn(Modifier.width(160.dp).fillMaxHeight()) {
        items(reader.pageCount) { i ->
            PdfThumbnail(state = reader, pageIndex = i, modifier = Modifier.clickable { /* jumpToPage(i) */ })
        }
    }
    PdfReader(state = reader, modifier = Modifier.weight(1f).fillMaxHeight())
}

Zoom et ajustement à la largeur

PdfReaderState.renderScale est un simple Float que chaque PdfPage observe. 1.0 correspond à l'ajustement à la largeur ; changez-le pour re-rendre les pages visibles à la nouvelle taille :

var scale by remember { mutableStateOf(1f) }
LaunchedEffect(scale) { reader.renderScale = scale }

Slider(value = scale, onValueChange = { scale = it }, valueRange = 0.5f..3f)

Extraire du texte par programmation

pageText renvoie l'Unicode d'une seule page ; pageTextLayout renvoie des rectangles au niveau des lignes pour la recherche et le surlignage :

scope.launch {
    val text = reader.pageText(pageIndex = 0)
    println(text)
}

L'app d'exemple de la bibliothèque câble un sélecteur, une barre latérale, le zoom et la sélection dans un écran de lecture complet. Voir le module :example du dépôt pour une référence complète.

Et ensuite