PDF Viewer KMP(基于 chrome 的 PDFium 内核)

447 阅读6分钟

PDF Viewer KMP

Github地址

支持 Context7 ,AI直接能读懂最准确的用法。

库English简体中文
pdf-coreUsing PDF CorePDF Core 使用指南
pdf-viewerUsing PDF ViewerPDF Viewer 使用指南
API 参考Dokka—

前言:跨平台 PDF 的“老大难”与内核选型

在做跨平台(Kotlin Multiplatform / Compose Multiplatform)开发时,PDF 的渲染与交互一直是一块极其让人头疼的“硬骨头”。

虽然各平台看似都有现成的解决方案,但实际落地时会发现处处是坑:

  • 平台 API 割裂且能力参差:Android 系统的原生 PdfRenderer 功能极其简陋,缺少高阶文本交互,复杂文档还容易出现 OOM 或渲染异常;iOS 虽有成熟的 PDFKit,但行为与 Android 完全不同;桌面端(macOS / Windows / Linux)缺乏统一且开箱即用的轻量级方案;Web 端更是往往不得不引入庞大臃肿的 JS 库。
  • 渲染与坐标的一致性极差:不同平台底层引擎不同,字体渲染排版、页面旋转、视口缩放计算、文本字符的坐标矩形(Quad/Bounds)都会存在细微偏差,导致跨端的搜索高亮、跳转交互几乎不可能有一致的体验。

为了彻底抹平不同平台的差异,在为 KMP 生态打造 PDF Viewer KMP 时,我最终选定了 Google Chrome 同款的开源 PDF 引擎 —— PDFium 作为全平台的统一底层内核。


为什么选择 Chrome 的 PDFium?它的核心优势是什么?

很多开发者提到 Chrome 内核,第一反应是庞大的 Chromium 浏览器或者 Blink 排版引擎,而实际上,PDFium 是 Google 从 Chromium 中抽离并持续维护的高性能、纯 C++ 开源 PDF 渲染库(其前身是 Google 与福昕 Foxit 合作开源的成熟引擎)。

选型 PDFium 的主要优势有以下几点:

  1. 工业级的稳定性与规范兼容度(Chrome 背书) 作为 Chrome 浏览器多年以来的默认内置 PDF 引擎,PDFium 每天承受全球数十亿用户以及海量结构怪异、复杂排版、老旧编码的极端 PDF 文档考验。相比市面上许多第三方小众解析库,它对 PDF 规范的兼容性、防崩溃能力以及容错度均属于工业级水准。

  2. 跨平台“像素级”渲染一致性 PDF 渲染最忌讳“千端千面”。选用 PDFium 后,Android、iOS、JVM 桌面端(macOS/Linux/Windows)以及浏览器端(JS/Wasm)全部共享同一套 C++ 核心逻辑。不仅视觉渲染效果、字体抗锯齿表现完全一致,文本提取、关键词搜索、字符坐标高亮及超链接解析的底层逻辑也是 100% 统一的,彻底终结了平台间的兼容性地狱。

  3. 极致的运行时性能与更低的内存开销 PDFium 采用原生 C++ 编写,在内存复用、光栅化(Rasterization)速度和指令集优化上极为激进。相较于用 WebView 套壳或纯 JavaScript 解析(如重量级 PDF.js),原生 PDFium 启动速度快得多、CPU 开销小、内存占用可控,非常适合高帧率的缩放、平移和懒加载列表渲染。

  4. 真正的全平台编译支持(覆盖 WebAssembly) PDFium 的可移植性非常强:

    • 在 Android 上通过 NDK/JNI 编译;
    • 在 Desktop(JVM)上无缝打包动态库原生运行;
    • 在 iOS 上可静态编译打包(为 KMP cinterop 提供自包含静态归档,免去分发 dylib 和签名的麻烦);
    • 在 Web 端更可通过 Emscripten 编译为高效的 WebAssembly (Wasm),让浏览器端同样拥有接近原生的渲染性能与完全一致的能力集。

正因如此,基于 PDFium 构建底层核心(pdf-core)并在此之上封装声明式的 Compose Multiplatform 交互组件(pdf-viewer),成为了一套高内聚、高一致性、全平台可用的理想架构。接下来,本文将分享如何在 Kotlin Multiplatform 项目中集成和使用这套方案。

Demo

下载已签名的 Android 示例应用:pdf-viewer-kmp-demo.apk。

以下截图来自在 OPPO Android 真机上运行的 Demo:

Demo 首页PDF 阅读器搜索与高亮
demo-home.pngpdf-viewer.pngpdf-search.png

项目包含两个公开库:

库用途
pdf-core打开 PDF 文档,提供元数据、页面渲染、文本、搜索和链接等不依赖 Compose UI 的 API。
pdf-viewer提供 Compose Multiplatform PdfView 组件,并传递依赖 pdf-core。

支持平台

平台支持目标
Androidarm32、arm64、x86、x64
iOS真机 arm64、模拟器 arm64
JVM 桌面端macOS arm64/x64、Linux x64、Windows x64
浏览器JavaScript、Wasm

目前不支持 iOS x64 和 Catalyst。内置 PDFium 固定为 chromium/7961 版本,不包含 V8 和 XFA。

添加依赖

添加 Maven Central,并在 commonMain 中选择需要的库:

repositories {
    mavenCentral()
}

kotlin {
    sourceSets {
        commonMain.dependencies {
            // 只使用 PDF 底层 API:
            implementation("io.github.limuyang2:pdf-core:0.3.0")

            // 或使用 Compose Viewer,pdf-core 会被传递引入:
            implementation("io.github.limuyang2:pdf-viewer:0.3.0")
        }
    }
}

快速开始:PDF Core

应用只需要读取或渲染 PDF、不需要内置 Compose UI 时,可直接使用 pdf-core:

import io.github.limuyang2.pdf.core.PdfPixelSize
import io.github.limuyang2.pdf.core.PdfRenderRequest
import io.github.limuyang2.pdf.core.PdfSource
import io.github.limuyang2.pdf.core.PdfViewer

suspend fun renderFirstPage(pdfBytes: ByteArray): ByteArray {
    val document = PdfViewer.open(PdfSource.Bytes(pdfBytes))
    try {
        require(document.pageCount > 0)

        val bitmap =
            document[0].render(
                PdfRenderRequest(
                    outputSize = PdfPixelSize(1200, 1600),
                ),
            )
        try {
            return bitmap.copyPixels()
        } finally {
            bitmap.close()
        }
    } finally {
        document.closeAndAwait()
    }
}

密码、元数据、文本、搜索、链接、位图格式、资源生命周期、异常和各平台配置, 请参阅 PDF Core 使用指南。

快速开始:PDF Viewer

pdf-viewer 使用纵向滚动的 Compose 页面列表显示整个文档:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.github.limuyang2.pdf.core.PdfDocument
import io.github.limuyang2.pdf.viewer.PdfView
import io.github.limuyang2.pdf.viewer.rememberPdfViewState

@Composable
fun DocumentPreview(document: PdfDocument) {
    val state = rememberPdfViewState()

    PdfView(
        document = document,
        state = state,
        modifier = Modifier.fillMaxSize(),
        maxZoom = 4f,
        gestureZoomEnabled = true,
    )
}

调用方拥有 PdfDocument,必须在页面销毁时关闭它。PdfView 只负责关闭内部 临时渲染的位图。

在 Compose 中打开文档、状态控制、缩放配置、渲染尺寸限制、自定义加载和错误 UI、链接处理等内容,请参阅 PDF Viewer 使用指南。

各平台配置概览

  • Android: 支持 API 24 及更高版本,PDFium JNI runtime 会自动加载。
  • JVM: 原生 PDFium 库已内置,会自动解压到系统临时目录。部分 JVM 运行时可能需要 --enable-native-access=ALL-UNNAMED。
  • 浏览器: 需要在配置的 PDFium 静态资源目录中部署 manifest.properties、pdfium-adapter.js、pdfium.js 和 pdfium.wasm。
  • iOS: 最低部署版本为 iOS 14.0。PDFium 已通过发布的 KLIB 静态链接, 应用无需额外嵌入动态库。

详细配置参见 PDF Core 使用指南。

当前能力

所有当前后端均支持:

  • 字节数组输入源和密码保护文档;
  • 文档信息、权限、元数据和页面标签;
  • 页面尺寸、固有旋转方向和边界框;
  • 完整页面 BGRA8888 渲染;
  • 基础文本提取;
  • 支持大小写、整词和连续匹配选项的文本搜索;
  • 内部跳转目标、URI 动作和链接标注区域。

调用可选 API 前,可以通过 PdfViewer.capabilities 检查当前平台能力。

当前尚未实现:书签、内嵌缩略图、公开后端的文本布局几何信息、随机访问输入源、 局部裁剪渲染、表单、编辑、渐进式加载或渲染、JavaScript 和 XFA。不支持的操作 会抛出 PdfUnsupportedFeatureException。

许可证

MIT