You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Compose Desktop应用的UI界面中直接渲染PDF文件

Jetpack Compose Desktop Windows平台内置PDF渲染实现方案

1. 依赖引入

选用Apache PDFBox作为PDF处理核心,它对Windows平台兼容性好,无需依赖系统原生PDF组件。在你的build.gradle.kts dependencies块中添加以下依赖:

dependencies {
    // PDFBox核心依赖
    implementation("org.apache.pdfbox:pdfbox:2.0.32")
    // 可选:处理字体缺失问题,避免中文渲染乱码
    implementation("org.apache.pdfbox:fontbox:2.0.32")
}

2. 封装PDF渲染工具类

实现将PDF页面转为Compose可用的ImageBitmap的工具逻辑:

import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.rendering.PDFRenderer
import java.awt.image.BufferedImage
import java.io.File

class PdfRenderer(private val file: File) {
    private val document = PDDocument.load(file)
    private val renderer = PDFRenderer(document)
    val pageCount = document.numberOfPages

    // 渲染指定页码的PDF为ImageBitmap,dpi参数控制渲染清晰度
    fun renderPage(pageIndex: Int, dpi: Float = 144f): ImageBitmap {
        require(pageIndex in 0 until pageCount) { "页码超出范围" }
        val bufferedImage = renderer.renderImageWithDPI(pageIndex, dpi, BufferedImage.TYPE_INT_ARGB)
        return bufferedImage.asImageBitmap()
    }

    fun close() {
        document.close()
    }
}

3. 实现Compose PDF展示组件

封装可交互的PDF浏览组件,支持翻页、加载状态展示:

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import java.io.File

@Composable
fun PdfViewer(pdfFile: File, modifier: Modifier = Modifier) {
    var currentPage by remember { mutableStateOf(0) }
    var pageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
    var pdfRenderer by remember { mutableStateOf<PdfRenderer?>(null) }

    LaunchedEffect(pdfFile) {
        pdfRenderer?.close()
        pdfRenderer = PdfRenderer(pdfFile)
        currentPage = 0
    }

    LaunchedEffect(currentPage, pdfRenderer) {
        pageBitmap = pdfRenderer?.renderPage(currentPage)
    }

    Box(modifier = modifier) {
        pageBitmap?.let { bitmap ->
            Image(
                bitmap = bitmap,
                contentDescription = "PDF Page ${currentPage + 1}",
                modifier = Modifier.fillMaxSize()
            )
        } ?: Text("加载中...", modifier = Modifier.align(Alignment.Center))

        pdfRenderer?.let { renderer ->
            Row(
                modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter).padding(16.dp),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Button(
                    onClick = { if (currentPage > 0) currentPage-- },
                    enabled = currentPage > 0
                ) {
                    Text("上一页")
                }
                Text("${currentPage + 1} / ${renderer.pageCount}", modifier = Modifier.align(Alignment.CenterVertically))
                Button(
                    onClick = { if (currentPage < renderer.pageCount - 1) currentPage++ },
                    enabled = currentPage < renderer.pageCount - 1
                ) {
                    Text("下一页")
                }
            }
        }
    }
}

4. 调用示例

在主窗口中调用组件,可结合文件选择器加载本地PDF:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import javax.swing.JFileChooser
import javax.swing.filechooser.FileNameExtensionFilter

fun main() = application {
    Window(title = "PDF 浏览器") {
        var selectedPdf by remember { mutableStateOf<File?>(null) }

        if (selectedPdf == null) {
            Button(
                onClick = {
                    val chooser = JFileChooser().apply {
                        fileFilter = FileNameExtensionFilter("PDF 文件", "pdf")
                    }
                    val result = chooser.showOpenDialog(null)
                    if (result == JFileChooser.APPROVE_OPTION) {
                        selectedPdf = chooser.selectedFile
                    }
                },
                modifier = Modifier.align(Alignment.Center)
            ) {
                Text("选择PDF文件")
            }
        } else {
            PdfViewer(
                pdfFile = selectedPdf!!,
                modifier = Modifier.fillMaxSize()
            )
        }
    }
}

注意事项

  • 大体积PDF建议按需加载页面,不要一次性渲染所有页,避免内存溢出
  • 若遇到中文/特殊字体渲染乱码,可将对应字体文件打包到应用资源中,通过PDFBox的字体加载逻辑注册即可
  • 高DPI屏幕下可调整renderPage方法的dpi参数,提升渲染清晰度,默认96dpi是屏幕基础分辨率,144dpi对应150%缩放

内容的提问来源于stack exchange,提问作者george Yaney

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 14:39:00