news 2026/2/14 10:15:14

Flutter PDF 渲染插件(pdf_image_renderer)适配鸿蒙 (HarmonyOS) 平台实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter PDF 渲染插件(pdf_image_renderer)适配鸿蒙 (HarmonyOS) 平台实战

本文详细记录了将pdf_image_rendererFlutter 插件从 Android/iOS 适配到鸿蒙 (HarmonyOS/OpenHarmony) 平台的完整过程,包括技术方案选型、NAPI 原生模块开发、pdfium 库集成等核心内容。

一、项目背景

1.1 pdf_image_renderer 插件简介

pdf_image_renderer是一个 Flutter 插件,用于将 PDF 文件渲染为位图图片。它使用各平台的原生渲染器来实现高质量的 PDF 渲染:

  • Android: 使用android.graphics.pdf.PdfRenderer
  • iOS: 使用CGPDFDocument/CGPDFPage
  • HarmonyOS: 使用pdfium库通过 NAPI 实现(本文重点)

1.2 为什么需要适配鸿蒙?

随着 HarmonyOS NEXT 的发布,越来越多的开发者需要将现有的 Flutter 应用适配到鸿蒙平台。然而,鸿蒙系统没有内置的 PDF 渲染 API(不像 Android 的PdfRenderer或 iOS 的CGPDFDocument),这给 PDF 相关功能的适配带来了挑战。

二、技术方案设计

2.1 方案调研

经过调研,鸿蒙平台渲染 PDF 的可选方案有:

方案优点缺点
WebView 渲染实现简单性能差,无法获取位图数据
纯 Dart PDF 库跨平台渲染质量一般,性能受限
pdfium + NAPI高性能,渲染质量好需要 C++ 开发,复杂度较高

最终选择pdfium + NAPI方案,因为它能提供与 Android/iOS 原生渲染器同等质量的 PDF 渲染效果。

2.2 整体架构

┌─────────────────────────────────────────────────────────┐ │ Flutter (Dart) │ │ PdfImageRenderer API │ └─────────────────────┬───────────────────────────────────┘ │ MethodChannel ▼ ┌─────────────────────────────────────────────────────────┐ │ ArkTS Plugin Layer │ │ PdfImageRendererPlugin.ets │ │ - 处理 MethodChannel 调用 │ │ - 调用 NAPI 函数 │ │ - PNG 图片编码 (@ohos.multimedia.image) │ └─────────────────────┬───────────────────────────────────┘ │ NAPI Import ▼ ┌─────────────────────────────────────────────────────────┐ │ C++ NAPI Layer │ │ libpdf_renderer.so │ │ - PDF 文档管理 │ │ - 页面渲染 │ │ - BGRA → RGBA 像素转换 │ └─────────────────────┬───────────────────────────────────┘ │ 动态链接 ▼ ┌─────────────────────────────────────────────────────────┐ │ libpdfium.so │ │ (预编译 PDF 渲染引擎) │ └─────────────────────────────────────────────────────────┘

三、实现详解

3.1 目录结构

ohos/ ├── oh-package.json5 # 包配置 ├── build-profile.json5 # 构建配置(含 CMake) ├── Index.ets # 入口文件 ├── src/main/ │ ├── module.json5 # 模块配置 │ ├── ets/components/plugin/ │ │ └── PdfImageRendererPlugin.ets # ArkTS 插件主类 │ ├── cpp/ │ │ ├── CMakeLists.txt # C++ 构建配置 │ │ ├── napi_init.cpp # NAPI 模块注册 │ │ ├── pdf_renderer_napi.cpp # PDF 渲染实现 │ │ ├── pdf_renderer_napi.h │ │ ├── include/ │ │ │ └── fpdfview.h # pdfium 头文件 │ │ └── types/libpdf_renderer/ │ │ ├── index.d.ts # TypeScript 类型声明 │ │ └── oh-package.json5 │ └── libs/arm64-v8a/ │ └── libpdfium.so # 预编译 pdfium 库

3.2 NAPI 模块实现

3.2.1 模块注册 (napi_init.cpp)
staticnapi_valueInit(napi_env env,napi_value exports){napi_property_descriptor desc[]={{"openPdf",nullptr,NapiOpenPdf,nullptr,nullptr,nullptr,napi_default,nullptr},{"closePdf",nullptr,NapiClosePdf,nullptr,nullptr,nullptr,napi_default,nullptr},{"getPageCount",nullptr,NapiGetPageCount,nullptr,nullptr,nullptr,napi_default,nullptr},{"openPage",nullptr,NapiOpenPage,nullptr,nullptr,nullptr,napi_default,nullptr},{"closePage",nullptr,NapiClosePage,nullptr,nullptr,nullptr,napi_default,nullptr},{"getPageWidth",nullptr,NapiGetPageWidth,nullptr,nullptr,nullptr,napi_default,nullptr},{"getPageHeight",nullptr,NapiGetPageHeight,nullptr,nullptr,nullptr,napi_default,nullptr},{"renderPage",nullptr,NapiRenderPage,nullptr,nullptr,nullptr,napi_default,nullptr},};napi_define_properties(env,exports,sizeof(desc)/sizeof(desc[0]),desc);// 初始化 pdfium 库PdfRendererNapi::GetInstance().InitLibrary();returnexports;}
3.2.2 PDF 渲染核心逻辑
uint8_t*PdfRendererNapi::RenderPage(intdocId,intpageIndex,intx,inty,intwidth,intheight,doublescale,uint32_tbackgroundColor,int*outDataSize){// 1. 创建位图FPDF_BITMAP bitmap=FPDFBitmap_Create(scaledWidth,scaledHeight,1);// 2. 填充背景色FPDFBitmap_FillRect(bitmap,0,0,scaledWidth,scaledHeight,backgroundColor);// 3. 渲染页面到位图FPDF_RenderPageBitmap(bitmap,page,renderX,renderY,renderWidth,renderHeight,0,FPDF_ANNOT|FPDF_LCD_TEXT);// 4. BGRA → RGBA 转换(pdfium 输出 BGRA,但 HarmonyOS image API 需要 RGBA)uint8_t*src=(uint8_t*)FPDFBitmap_GetBuffer(bitmap);for(inti=0;i<scaledWidth*scaledHeight;i++){intoffset=i*4;outputData[offset+0]=src[offset+2];// R <- BoutputData[offset+1]=src[offset+1];// G <- GoutputData[offset+2]=src[offset+0];// B <- RoutputData[offset+3]=src[offset+3];// A <- A}returnoutputData;}

3.3 ArkTS 插件层

import{picker}from'@kit.CoreFileKit';import{fileUri}from'@kit.CoreFileKit';importimagefrom'@ohos.multimedia.image';importpdfRendererfrom'libpdf_renderer.so';exportdefaultclassPdfImageRendererPluginimplementsFlutterPlugin,MethodCallHandler{// 文件选择器privateasynchandlePickPdfFile(call:MethodCall,result:MethodResult):Promise<void>{constdocumentSelectOptions=newpicker.DocumentSelectOptions();documentSelectOptions.fileSuffixFilters=['.pdf','.PDF'];constdocumentPicker=newpicker.DocumentViewPicker();consturis=awaitdocumentPicker.select(documentSelectOptions);if(uris&&uris.length>0){constpath=newfileUri.FileUri(uris[0]).path;result.success(path);}}// 渲染 PDF 页面privateasynchandleRenderPDFPage(call:MethodCall,result:MethodResult):Promise<void>{// 1. 调用 NAPI 渲染获取 RGBA 像素数据constrenderResult=pdfRenderer.renderPage(docId,pageIndex,x,y,width,height,scale,bgColor);// 2. 创建 PixelMapconstpixelMap=awaitimage.createPixelMap(renderResult.data.buffer,{size:{width:renderResult.width,height:renderResult.height},pixelFormat:image.PixelMapFormat.RGBA_8888,});// 3. 编码为 PNGconstpacker=image.createImagePacker();constpngBuffer=awaitpacker.packing(pixelMap,{format:'image/png',quality:100});result.success(newUint8Array(pngBuffer));}}

3.4 CMake 配置(关键!)

预编译库的正确引入方式:

# 错误方式(库不会被打包到 HAP) link_directories(${PDFIUM_LIB_PATH}) target_link_libraries(pdf_renderer pdfium) # 正确方式(库会被自动打包到 HAP) add_library(pdfium SHARED IMPORTED) set_target_properties(pdfium PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../libs/${OHOS_ARCH}/libpdfium.so) target_link_libraries(pdf_renderer PUBLIC pdfium)

四、遇到的问题与解决方案

4.1 pdfium 库获取

问题:鸿蒙平台没有预编译的 pdfium 库。

解决:从 bblanchon/pdfium-binaries 下载linux-musl-arm64版本,该版本与 HarmonyOS 的 musl libc 兼容。

curl-L -o pdfium.tgz https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-musl-arm64.tgz

4.2 NAPI 模块加载失败

问题:运行时报错Cannot read property openPdf of undefined

原因libpdfium.so没有被打包到 HAP 中。

解决:使用 CMake 的IMPORTED库方式引入预编译库,确保其被正确打包。

4.3 file_picker 不支持 OHOS

问题:Flutter 的file_picker包不支持 OHOS 平台。

解决:在插件中实现原生文件选择器,使用@kit.CoreFileKitpickerAPI。

五、使用方法

5.1 安装

dependencies:pdf_image_renderer:^1.0.1

5.2 基本使用

// 1. 创建渲染器finalpdf=PdfImageRenderer(path:'/path/to/document.pdf');// 2. 打开 PDFawaitpdf.open(password:'optional_password');// 3. 获取页数finalpageCount=awaitpdf.getPageCount();// 4. 渲染页面为图片finalimageData=awaitpdf.renderPage(pageIndex:0,scale:2.0,// 2倍分辨率background:Colors.white,);// 5. 显示图片Image(image:MemoryImage(imageData!))// 6. 关闭 PDFawaitpdf.close();

5.3 OHOS 平台文件选择

// 使用原生文件选择器(仅 OHOS)finalpath=awaitPdfImageRendererPlatform.instance.pickPdfFile();if(path!=null){finalpdf=PdfImageRenderer(path:path);awaitpdf.open();// ...}

六、性能数据

测试项设备结果
打开 10 页 PDFHarmonyOS NEXT~50ms
渲染单页 (A4, scale=1)HarmonyOS NEXT~30ms
渲染单页 (A4, scale=3)HarmonyOS NEXT~150ms
内存占用-约 20MB (含 pdfium)

七、总结

本次适配工作的主要成果:

  1. 完整实现PDF 渲染功能,包括打开/关闭文档、获取页数、获取尺寸、渲染页面
  2. 支持密码保护的 PDF 文件
  3. 支持自定义渲染参数:缩放比例、裁剪区域、背景颜色
  4. 原生文件选择器集成
  5. 高性能渲染:使用 pdfium 引擎,渲染质量与 Chrome 浏览器一致

关键技术点

  • OHOS NAPI (Node-API) 开发
  • CMake 预编译库集成
  • ArkTS 与 C++ 混合编程
  • Flutter MethodChannel 通信
  • HarmonyOS 多媒体 API (image 模块)

源码仓库

  • 插件地址:pdf_image_renderer

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/11 7:08:55

Qwen3-0.6B本地运行指南:保护隐私的同时玩AI

Qwen3-0.6B本地运行指南&#xff1a;保护隐私的同时玩AI 【一键部署镜像】Qwen3-0.6B 轻量、安全、开箱即用的本地大模型体验——无需联网调用云端API&#xff0c;所有数据留在你自己的设备上 镜像地址&#xff1a;CSDN星图镜像广场 → 搜索“Qwen3-0.6B” 1. 为什么要在本地…

作者头像 李华
网站建设 2026/2/10 19:48:50

Blender 3MF文件处理完整指南:从入门到高效处理

Blender 3MF文件处理完整指南&#xff1a;从入门到高效处理 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 3MF文件格式作为3D打印领域的重要标准&#xff0c;能够完整保…

作者头像 李华
网站建设 2026/2/8 20:31:33

【QtScrcpy】:3个超实用步骤安卓投屏指南

【QtScrcpy】&#xff1a;3个超实用步骤安卓投屏指南 【免费下载链接】QtScrcpy QtScrcpy 可以通过 USB / 网络连接Android设备&#xff0c;并进行显示和控制。无需root权限。 项目地址: https://gitcode.com/GitHub_Trending/qt/QtScrcpy QtScrcpy是一款无需root权限即…

作者头像 李华
网站建设 2026/2/8 17:34:50

TouchGFX中触摸响应优化全面讲解:低延迟交互设计要点

以下是对您提供的博文内容进行 深度润色与重构后的技术文章 。我以一位深耕嵌入式GUI多年、主导过多个车规级HMI项目落地的工程师视角,彻底重写了全文—— 去除所有AI腔调与模板化表达,强化工程语感、实战细节与逻辑纵深;打破“章节堆砌”,让技术流自然流淌;删除空泛总…

作者头像 李华
网站建设 2026/2/6 10:21:31

英雄联盟辅助工具:从青铜到钻石的效率革命

英雄联盟辅助工具&#xff1a;从青铜到钻石的效率革命 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari 还在为匹配成功时手忙…

作者头像 李华
网站建设 2026/2/10 4:01:41

解锁DLSS调试功能:DLSS Swapper的可视化指示器配置指南

解锁DLSS调试功能&#xff1a;DLSS Swapper的可视化指示器配置指南 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 你是否曾在游戏中开启DLSS后&#xff0c;却不确定它是否真的在工作&#xff1f;就像开车时仪表盘突然…

作者头像 李华