Flutter PDF处理插件pdfrx_fork的使用
Flutter PDF处理插件pdfrx_fork的使用
注意事项
本插件是原始插件pdfrx的一个克隆版本,包含了一些优化代码以支持Flutter < 3.19。您可以查看原始插件在以下链接:
原始插件
pdfrx 是一个基于 pdfium 构建的丰富的快速PDF查看器实现。该插件支持Android、iOS、Windows、macOS、Linux,但不支持Web。
交互式演示
一个使用Flutter Web的演示站点:
主要功能
- 可缩放和滚动的PDF文档查看器
 - 支持PDF链接处理
 - 支持文档大纲(即书签)
 - 支持文本选择(仍在实验中)
 - 支持文本搜索
 - 高度可定制化
 - 多平台支持
- Android
 - iOS
 - Windows
 - macOS
 - Linux(甚至可以在Raspberry PI上运行)
 
 - 三层API
- 易用的Flutter小部件
PdfViewerPdfDocumentViewBuilderPdfPageView
 - 易用的PDF API
PdfDocument
 - pdfium绑定
- 不鼓励使用,但你可以导入 
package:pdfrx/src/pdfium/pdfium_bindings.dart 
 - 不鼓励使用,但你可以导入 
 
 - 易用的Flutter小部件
 
入门指南
以下片段展示了如何在资产中显示PDF文件:
class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Pdfrx example'),
        ),
        body: PdfViewer.asset('assets/hello.pdf'),
      ),
    );
  }
}
安装
将以下内容添加到您的包的pubspec.yaml文件中,并执行flutter pub get:
dependencies:
  pdfrx: ^0.0.53
Windows
确保启用开发人员模式。 在构建过程中,内部使用了符号链接,这需要启用开发者模式。如果不启用,可能会遇到错误,如 此链接 所示。
Web
现在会自动加载pdf.js,无需修改index.html。但您可以自定义下载URLs来设置PdfJsConfiguration.configuration:
PdfJsConfiguration.configuration = const PdfJsConfiguration(
  pdfJsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.mjs',
  workerSrc: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js',
  cMapUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/cmaps/',
);
请注意,pdf.js 4.X 版本尚未支持,请使用3.X版本。
macOS
对于macOS,Flutter应用默认限制其功能通过启用 App Sandbox。您可以根据配置编辑您的应用的权限文件。例如:
处理App Sandbox
最简单的方法是禁用App Sandbox,尽管这在发布应用时不推荐使用。另一种方法是使用 com.apple.security.files.user-selected.read-only 和 file_selector_macos。以下是示例代码:
<dict>
  <key>com.apple.security.app-sandbox</key>
  <true/>
  <key>com.apple.security.network.client</key>
  <true/>
</dict>
打开PDF文件
PdfViewer 支持以下方法打开特定介质上的PDF文件:
PdfViewer.asset- 打开Flutter资源中的PDF文件
 
PdfViewer.file- 打开本地文件中的PDF文件
 
PdfViewer.uri- 从URI(
https://...或相对路径)打开PDF文件 - 在Flutter Web上,可能会被CORS阻止
 
- 从URI(
 
处理密码保护的PDF文件
要支持密码保护的PDF文件,可以使用 passwordProvider 交互地提供密码:
PdfViewer.asset(
  'assets/test.pdf',
  // 设置密码提供者以显示密码对话框
  passwordProvider: _passwordDialog,
  ...
),
_passwordDialog 函数定义如下:
Future<String?> _passwordDialog() async {
  final textController = TextEditingController();
  return await showDialog<String>(
    context: context,
    barrierDismissible: false,
    builder: (context) {
      return AlertDialog(
        title: const Text('Enter password'),
        content: TextField(
          controller: textController,
          autofocus: true,
          keyboardType: TextInputType.visiblePassword,
          obscureText: true,
          onSubmitted: (value) => Navigator.of(context).pop(value),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(null),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop(textController.text),
            child: const Text('OK'),
          ),
        ],
      );
    },
  );
}
当 PdfViewer 尝试打开一个密码保护的文档时,它会重复调用传递给 passwordProvider 的函数(第一次尝试除外),直到成功打开文档。如果该函数返回null,则查看器将放弃密码尝试,且该函数不再被调用。
第一次尝试使用空密码
默认情况下,第一次密码尝试使用空密码。这是因为加密的PDF文件通常使用空密码用于查看目的。这在大多数情况下是有用的,但如果需要使用作者密码,可以通过设置 firstAttemptByEmptyPassword 为false来禁用。
自定义化
您可以使用 PdfViewerParams 来自定义行为和视觉效果。
文本选择
文本选择功能仍在实验中,但您可以轻松启用它:
PdfViewer.asset(
  'assets/test.pdf',
  enableTextSelection: true,
  ...
),
PDF链接处理
要启用PDF文件中的链接,应设置 PdfViewerParams.linkWidgetBuilder:
linkWidgetBuilder: (context, link, size) => Material(
  color: Colors.transparent,
  child: InkWell(
    onTap: () {
      // 处理URL或目的地
      if (link.url != null) {
        // TODO: 实现自己的isSecureUrl
        if (await isSecureUrl(link.url!)) {
          launchUrl(link.url!);
        }
      } else if (link.dest != null) {
        controller.goToDest(link.dest);
      }
    },
    hoverColor: Colors.blue.withOpacity(0.2),
  ),
),
对于URI,应在打开URI之前检查其有效性;示例代码只是弹出对话框询问是否打开URL。
对于目的地,您可以使用 PdfViewerController.goToDest 转到目的地。或者,您可以使用 PdfViewerController.calcMatrixForDest 获取矩阵。
文档大纲(书签)
PDF定义了文档大纲(PdfOutlineNode),有时也称为书签或索引。您可以使用 PdfDocument.loadOutline 访问它。
以下片段在 PdfViewerParams.onViewerReady 上获取它:
onViewerReady: (document, controller) async {
  outline.value = await document.loadOutline();
},
PdfOutlineNode 是树状结构的数据,更多信息见 示例代码。
横向滚动视图
默认情况下,页面布局为垂直方向。您可以使用 PdfViewerParams.layoutPages 自定义布局逻辑:
layoutPages: (pages, params) {
  final height = pages.fold(0.0, (prev, page) => max(prev, page.height)) + params.margin * 2;
  final pageLayouts = <Rect>[];
  double x = params.margin;
  for (var page in pages) {
    pageLayouts.add(
      Rect.fromLTWH(
        x,
        (height - page.height) / 2, // 垂直居中
        page.width,
        page.height,
      ),
    );
    x += page.width + params.margin;
  }
  return PdfPageLayout(
    pageLayouts: pageLayouts,
    documentSize: Size(x, height),
  );
},
对面页
以下代码将展示“对面顺序布局”,这是PDF查看器应用中常用的布局方式:
/// 页面阅读顺序;true表示L到R,通常是书籍使用的顺序
var isRightToLeftReadingOrder = false;
/// 使用第一页作为封面页
var needCoverPage = true;
...
layoutPages: (pages, params) {
  final width = pages.fold(
      0.0, (prev, page) => max(prev, page.width));
  final pageLayouts = <Rect>[];
  final offset = needCoverPage ? 1 : 0;
  double y = params.margin;
  for (int i = 0; i < pages.length; i++) {
    final page = pages[i];
    final pos = i + offset;
    final isLeft = isRightToLeftReadingOrder
        ? (pos & 1) == 1
        : (pos & 1) == 0;
    final otherSide = (pos ^ 1) - offset;
    final h = 0 <= otherSide && otherSide < pages.length
        ? max(page.height, pages[otherSide].height)
        : page.height;
    pageLayouts.add(
      Rect.fromLTWH(
        isLeft
            ? width + params.margin - page.width
            : params.margin * 2 + width,
        y + (h - page.height) / 2,
        page.width,
        page.height,
      ),
    );
    if (pos & 1 == 1 || i + 1 == pages.length) {
      y += h + params.margin;
    }
  }
  return PdfPageLayout(
    pageLayouts: pageLayouts,
    documentSize: Size(
      (params.margin + width) * 2 + params.margin,
      y,
    ),
  );
},
显示滚动条
默认情况下,查看器不会显示任何滚动条或滚动拇指。您可以使用 PdfViewerParams.viewerOverlayBuilder 添加滚动拇指:
viewerOverlayBuilder: (context, size) => [
  // 在查看器右侧添加垂直滚动条
  PdfViewerScrollThumb(
    controller: controller,
    orientation: ScrollbarOrientation.right,
    thumbSize: const Size(40, 25),
    thumbBuilder:
        (context, thumbSize, pageNumber, controller) =>
            Container(
      color: Colors.black,
      // 在拇指上显示页码
      child: Center(
        child: Text(
          pageNumber.toString(),
          style: const TextStyle(color: Colors.white),
        ),
      ),
    ),
  ),
  // 在查看器底部添加水平滚动条
  PdfViewerScrollThumb(
    controller: controller,
    orientation: ScrollbarOrientation.bottom,
    thumbSize: const Size(80, 30),
    thumbBuilder:
        (context, thumbSize, pageNumber, controller) =>
            Container(
      color: Colors.red,
    ),
  ),
],
在每页底部添加页码
如果您想在每页底部添加页码,可以使用 PdfViewerParams.pageOverlayBuilder:
pageOverlayBuilder: (context, pageRect, page) {
  return Align(
    alignment: Alignment.bottomCenter,
    child: Text(page.pageNumber.toString(),
    style: const TextStyle(color: Colors.red))),
},
加载指示器
PdfViewer.uri 可能需要很长时间下载PDF文件,您可能希望显示一些加载指示器。您可以使用 PdfViewerParams.loadingBannerBuilder:
loadingBannerBuilder: (context, bytesDownloaded, totalBytes) {
  return Center(
    child: CircularProgressIndicator(
      // totalBytes可能在某些情况下不可用
      value: totalBytes != null ? bytesDownloaded / totalBytes : null,
      backgroundColor: Colors.grey,
    ),
  );
}
暗/夜模式支持
PdfViewer 本身没有原生的暗模式或夜模式支持,但可以使用 ColorFiltered 小部件轻松实现:
ColorFiltered(
  colorFilter: ColorFilter.mode(Colors.white, darkMode ? BlendMode.difference : BlendMode.dst),
  child: PdfViewer.file(filePath, ...),
),
该技巧最初由 pckimlong 引入。
其他功能
文本搜索
TextSearcher 是一个辅助类,帮助您在应用程序中实现文本搜索功能。
以下片段展示了 TextSearcher 的整体结构:
class _MainPageState extends State<MainPage> {
  final controller = PdfViewerController();
  // 创建一个 PdfTextSearcher 并添加一个监听器来更新GUI上的搜索结果变化
  late final textSearcher = PdfTextSearcher(controller)..addListener(_update);
  void _update() {
    if (mounted) {
      setState(() {});
    }
  }
  [@override](/user/override)
  void dispose() {
    // 释放 PdfTextSearcher
    textSearcher.removeListener(_update);
    textSearcher.dispose();
    super.dispose();
  }
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pdfrx example'),
      ),
      body: PdfViewer.asset(
        'assets/hello.pdf',
        controller: controller,
        params: PdfViewerParams(
          // 添加 pageTextMatchPaintCallback 以绘制搜索高亮
          pagePaintCallbacks: [
            textSearcher.pageTextMatchPaintCallback
          ],
        ),
      )
    );
  }
  ...
}
在此片段中,它执行以下操作:
- 创建 
TextSearcher实例 - 添加监听器(使用 
PdfTextSearcher.addListener)来更新UI上的搜索结果变化 - 将 
TextSearcher.pageTextMatchPaintCallback添加到PdfViewerParams.pagePaintCallbacks以显示搜索匹配项 
然后,您可以使用 TextSearcher.startTextSearch 在PDF文档中搜索文本:
textSearcher.startTextSearch('hello', caseInsensitive: true);
搜索将在后台运行,并通过监听器通知搜索进度。
您还可以使用以下函数导航用户到搜索匹配项:
TextSearcher.goToMatchOfIndex转到指定索引的匹配项TextSearcher.goToNextMatch转到下一个匹配项TextSearcher.goToPrevMatch转到上一个匹配项
您可以在搜索期间(即使搜索正在进行时)通过 PdfTextRange 列表获取搜索结果:
for (final match in textSearcher.matches) {
  print(match.pageNumber);
  ...
}
您还可以取消后台搜索:
textSearcher.resetTextSearch();
PdfDocumentViewBuilder/PdfPageView
PdfPageView 是另一个仅显示一页的PDF小部件。它接受 PdfDocument 和页码以显示文档内的一页。
PdfDocumentViewBuilder 用于在小部件树内安全管理 PdfDocument,并接受 builder 参数以创建子小部件。
以下片段是这些小部件的典型用法:
PdfDocumentViewBuilder.asset(
  'asset/test.pdf',
  builder: (context, document) => ListView.builder(
    itemCount: document?.pages.length ?? 0,
    itemBuilder: (context, index) {
      return Container(
        margin: const EdgeInsets.all(8),
        height: 240,
        child: Column(
          children: [
            SizedBox(
              height: 220,
              child: PdfPageView(
                document: document,
                pageNumber: index + 1,
                alignment: Alignment.center,
              ),
            ),
            Text(
              '${index + 1}',
            ),
          ],
        ),
      );
    },
  ),
),
PdfDocument 管理
PdfDocumentViewBuilder 可以接受来自 PdfViewer 的 PdfDocumentRef,以安全地共享相同的 PdfDocument 实例。更多信息见 示例/lib/thumbnails_view.dart。
完整示例代码
以下是完整的示例代码:
import 'package:flutter/material.dart';
import 'package:pdfrx_fork/pdfrx_fork.dart';
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PDF Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'PDF Example'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    final uri = Uri.parse('https://pdfobject.com/pdf/sample.pdf');
    final params = PdfViewerParams(
      backgroundColor: Colors.white,
      loadingBannerBuilder: (context, bytesDownloaded, totalBytes) {
        return const Center(child: CircularProgressIndicator.adaptive());
      },
    );
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: PdfViewer.uri(uri, params: params),
    );
  }
}
更多关于Flutter PDF处理插件pdfrx_fork的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter PDF处理插件pdfrx_fork的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
pdfrx_fork 是一个用于在 Flutter 应用中处理 PDF 文件的插件。它是 pdfrx 插件的一个分支版本,提供了类似的功能,但可能在某些方面进行了改进或修复。使用 pdfrx_fork 插件,你可以在 Flutter 应用中加载、显示、以及处理 PDF 文件。
以下是使用 pdfrx_fork 插件的基本步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml 文件中添加 pdfrx_fork 插件的依赖:
dependencies:
  flutter:
    sdk: flutter
  pdfrx_fork: ^0.1.0 # 请使用最新版本
然后运行 flutter pub get 来安装依赖。
2. 导入库
在你的 Dart 文件中导入 pdfrx_fork:
import 'package:pdfrx_fork/pdfrx_fork.dart';
3. 加载和显示 PDF 文件
你可以使用 PdfViewerController 来加载和显示 PDF 文件。以下是一个简单的示例:
import 'package:flutter/material.dart';
import 'package:pdfrx_fork/pdfrx_fork.dart';
class PdfViewerScreen extends StatefulWidget {
  @override
  _PdfViewerScreenState createState() => _PdfViewerScreenState();
}
class _PdfViewerScreenState extends State<PdfViewerScreen> {
  late PdfViewerController _pdfController;
  @override
  void initState() {
    super.initState();
    _pdfController = PdfViewerController();
    _loadPdf();
  }
  Future<void> _loadPdf() async {
    // 从网络加载 PDF 文件
    final pdfDocument = await PdfDocument.openAsset('assets/sample.pdf');
    _pdfController.loadDocument(pdfDocument);
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PDF Viewer'),
      ),
      body: PdfViewer(
        controller: _pdfController,
      ),
    );
  }
}
4. 处理 PDF 文件
pdfrx_fork 提供了多种方法来处理 PDF 文件,例如:
- 
从网络加载 PDF:
final pdfDocument = await PdfDocument.openUrl('https://example.com/sample.pdf'); - 
从文件系统加载 PDF:
final pdfDocument = await PdfDocument.openFile('/path/to/sample.pdf'); - 
从 Asset 加载 PDF:
final pdfDocument = await PdfDocument.openAsset('assets/sample.pdf'); - 
获取 PDF 页数:
final pageCount = pdfDocument.pageCount; 
5. 自定义 PDF 查看器
PdfViewer 组件提供了多种自定义选项,例如:
- 缩放:你可以通过 
PdfViewerController控制 PDF 的缩放比例。 - 页面导航:你可以使用 
PdfViewerController跳转到指定的页面。 - 背景颜色:你可以通过 
backgroundColor属性设置背景颜色。 
6. 释放资源
在使用完 PdfDocument 后,记得释放资源:
pdfDocument.close();
7. 处理错误
在加载或处理 PDF 文件时,可能会遇到错误。你可以使用 try-catch 来捕获和处理这些错误:
try {
  final pdfDocument = await PdfDocument.openAsset('assets/sample.pdf');
  _pdfController.loadDocument(pdfDocument);
} catch (e) {
  print('Failed to load PDF: $e');
}
        
      
            
            
            
