PDFx

FlutterWebmacOS 10.11+Android 5.0+iOSWindows 上渲染和显示 PDF 文档。

包含 2 个 API

  • renderer 处理 PDF 文档、页面,并将页面渲染为图像
  • viewer 一系列 Flutter 小部件和控制器,用于显示渲染结果

pub package

展示

PdfViewPinch PdfView

入门

在您的 Flutter 项目中添加依赖项

flutter pub add pdfx

对于 Web,运行工具以自动将 pdfjs 库(CDN)添加到 index.html 文件中

flutter pub run pdfx:install_web

对于 Windows,运行工具以自动将 pdfium 版本属性覆盖到 CMakeLists.txt 文件中

flutter pub run pdfx:install_windows

使用示例

import 'package:pdfx/pdfx.dart';

final pdfPinchController = PdfControllerPinch(
  document: PdfDocument.openAsset('assets/sample.pdf'),
);

// Pdf view with re-render pdf texture on zoom (not loose quality on zoom)
// Not supported on windows
PdfViewPinch(
  controller: pdfPinchController,
);

//-- or --//

final pdfController = PdfController(
  document: PdfDocument.openAsset('assets/sample.pdf'),
);

// Simple Pdf view with one render of page (loose quality on zoom)
PdfView(
  controller: pdfController,
);

Viewer API

PdfController & PdfControllerPinch

参数 描述 默认值
document 要显示的文档
initialPage [PdfView] 创建时要显示的页面 1
viewportFraction 每个页面应占据视口的比例。 1.0

PdfView & PdfViewPinch

参数 描述 PdfViewPinch / PdfView
控制器 页面控件。请参阅 页面控件其他 PDF 信息 + / +
onPageChanged 每当中视口中心的页面发生变化时调用。请参阅 文档回调 + / +
onDocumentLoaded 文档加载时调用。请参阅 文档回调 + / +
onDocumentError 文档加载错误时调用。异常会作为属性传递 + / +
builders PDF 视图构建器集合。请参阅 自定义构建器 + / +
scrollDirection 页面翻转方向 + / +
renderer 自定义 PdfRenderer 选项。请参阅 自定义渲染器选项 – / +
pageSnapping 在 Web 上将鼠标滚轮滚动设置为 false – / +
physics 小部件如何响应用户输入 – / +
padding 每个页面的内边距。 + / –

PdfViewBuilders & PdfViewPinchBuilders

参数 描述 PdfViewPinchBuilders / PdfViewBuilders
选项 构建器的附加选项 + / +
documentLoaderBuilder PDF 文档加载时显示的 Widget + / +
pageLoaderBuilder PDF 页面加载时显示的 Widget + / +
errorBuilder 显示文档加载错误消息 + / +
builder 用于动画 PDF 加载状态的根视图构建器 + / +
pageBuilder 调用此回调为每个页面渲染 Widget。请参阅 自定义页面构建器 – / +

其他示例

打开其他文档

pdfController.openDocument(PdfDocument.openAsset('assets/sample.pdf'));

页面控件

// Jump to specified page
pdfController.jumpTo(3);

// Animate to specified page
_pdfController.animateToPage(3, duration: Duration(milliseconds: 250), curve: Curves.ease);

// Animate to next page 
_pdfController.nextPage(duration: Duration(milliseconds: 250), curve: Curves.easeIn);

// Animate to previous page
_pdfController.previousPage(duration: Duration(milliseconds: 250), curve: Curves.easeOut);

其他 PDF 信息

// Actual showed page
pdfController.page;

// Count of all pages in document
pdfController.pagesCount;

文档回调

PdfView(
  controller: pdfController,
  onDocumentLoaded: (document) {},
  onPageChanged: (page) {},
);

显示实际页码和总页数

PdfPageNumber(
  controller: _pdfController,
  // When `loadingState != PdfLoadingState.success`  `pagesCount` equals null_
  builder: (_, state, loadingState, pagesCount) => Container(
    alignment: Alignment.center,
    child: Text(
      '$page/${pagesCount ?? 0}',
      style: const TextStyle(fontSize: 22),
    ),
  ),
)

自定义渲染器选项

PdfView(
  controller: pdfController,
  renderer: (PdfPage page) => page.render(
    width: page.width * 2,
    height: page.height * 2,
    format: PdfPageImageFormat.jpeg,
    backgroundColor: '#FFFFFF',
  ),
);

自定义构建器

// Need static methods for builders arguments
class SomeWidget {
  static Widget builder(
    BuildContext context,
    PdfViewPinchBuilders builders,
    PdfLoadingState state,
    WidgetBuilder loadedBuilder,
    PdfDocument? document,
    Exception? loadingError,
  ) {
    final Widget content = () {
      switch (state) {
        case PdfLoadingState.loading:
          return KeyedSubtree(
            key: const Key('pdfx.root.loading'),
            child: builders.documentLoaderBuilder?.call(context) ??
                const SizedBox(),
          );
        case PdfLoadingState.error:
          return KeyedSubtree(
            key: const Key('pdfx.root.error'),
            child: builders.errorBuilder?.call(context, loadingError!) ??
                Center(child: Text(loadingError.toString())),
          );
        case PdfLoadingState.success:
          return KeyedSubtree(
            key: Key('pdfx.root.success.${document!.id}'),
            child: loadedBuilder(context),
          );
      }
    }();

    final defaultBuilder =
        builders as PdfViewPinchBuilders<DefaultBuilderOptions>;
    final options = defaultBuilder.options;

    return AnimatedSwitcher(
      duration: options.loaderSwitchDuration,
      transitionBuilder: options.transitionBuilder,
      child: content,
    );
  }

  static Widget transitionBuilder(Widget child, Animation<double> animation) =>
      FadeTransition(opacity: animation, child: child);

  static PhotoViewGalleryPageOptions pageBuilder(
    BuildContext context,
    Future<PdfPageImage> pageImage,
    int index,
    PdfDocument document,
  ) =>
      PhotoViewGalleryPageOptions(
        imageProvider: PdfPageImageProvider(
          pageImage,
          index,
          document.id,
        ),
        minScale: PhotoViewComputedScale.contained * 1,
        maxScale: PhotoViewComputedScale.contained * 3.0,
        initialScale: PhotoViewComputedScale.contained * 1.0,
        heroAttributes: PhotoViewHeroAttributes(tag: '${document.id}-$index'),
      );
}

PdfViewPinch(
  controller: pdfPinchController,
  builders: PdfViewPinchBuilders<DefaultBuilderOptions>(
    options: const DefaultBuilderOptions(
      loaderSwitchDuration: const Duration(seconds: 1),
      transitionBuilder: SomeWidget.transitionBuilder,
    ),
    documentLoaderBuilder: (_) =>
        const Center(child: CircularProgressIndicator()),
    pageLoaderBuilder: (_) =>
        const Center(child: CircularProgressIndicator()),
    errorBuilder: (_, error) => Center(child: Text(error.toString())),
    builder: SomeWidget.builder,
  ),
)

PdfView(
  controller: pdfController,
  builders: PdfViewBuilders<DefaultBuilderOptions>(
    // All from `PdfViewPinch` and:
    pageBuilder: SomeWidget.pageBuilder,
  ),
);

Renderer API

PdfDocument

参数 描述 默认值
sourceName toString 方法所需。包含打开文档(文件、数据或资产)的方法
id 文档唯一 ID。打开文档时生成。
pagesCount 文档中的总页数。从 1 开始。
isClosed 文档是否已关闭

本地文档打开

// From assets (Android, Ios, MacOs, Web)
final document = await PdfDocument.openAsset('assets/sample.pdf')

// From file (Android, Ios, MacOs)
final document = await PdfDocument.openFile('path/to/file/on/device')

// From data (Android, Ios, MacOs, Web)
final document = await PdfDocument.openData((FutureOr<Uint8List>) data)

网络文档打开

安装 [network_file] 包(支持所有平台)

flutter pub add internet_file

并使用它

import 'package:internet_file/internet_file.dart';

PdfDocument.openData(InternetFile.get('https://github.com/ScerIO/packages.flutter/raw/fd0c92ac83ee355255acb306251b1adfeb2f2fd6/packages/native_pdf_renderer/example/assets/sample.pdf'))

打开页面

final page = document.getPage(pageNumber); // Starts from 1

关闭文档

document.close();

PdfPage

参数 描述 默认值
document 父文档 Parent
id 页面唯一 ID。渲染和关闭页面所需。打开页面时生成。
width 页面源宽度(像素),整数
高度 页面源高度(像素),整数
isClosed 页面是否已关闭

渲染图像

final pageImage = page.render(
  // rendered image width resolution, required
  width: page.width * 2,
  // rendered image height resolution, required
  height: page.height * 2,

  // Rendered image compression format, also can be PNG, WEBP*
  // Optional, default: PdfPageImageFormat.PNG
  // Web not supported
  format: PdfPageImageFormat.JPEG,

  // Image background fill color for JPEG
  // Optional, default '#ffffff'
  // Web not supported
  backgroundColor: '#ffffff',

  // Crop rect in image for render
  // Optional, default null
  // Web not supported
  cropRect: Rect.fromLTRB(left, top, right, bottom),
);

PdfPageImage

参数 描述 默认值
id 页面唯一 ID。渲染和关闭页面所需。渲染页面时生成。
pageNumber 页码。第一页是 1。
width 渲染区域的宽度(像素),整数
高度 渲染区域的高度(像素),整数
bytes 渲染的图像结果,Uint8List
format 渲染的图像压缩格式,Web 端始终为 PNG PdfPageImageFormat.PNG

关闭页面

在打开新页面之前,Android 会要求关闭之前的页面。如果未执行此操作,应用程序可能会因错误而崩溃

page.close();

* WEBP 格式的 PdfPageImage 仅在 Android 上支持

渲染附加信息

在 Web 上

此插件使用 PDF.js

在 Android 上

此插件使用 Android 原生的 PdfRenderer

在 iOS 和 macOS 上

此插件使用 iOS 和 macOS 原生的 CGPDFPage

在 Windows 上

此插件使用 PDFium

GitHub

查看 Github