Flutter PDF渲染插件pdfium_bindings的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter PDF渲染插件pdfium_bindings的使用

pdfium_bindings

本项目旨在通过FFI(Foreign Function Interface)在dart中完全封装Pdfium API。

低级示例

import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:image/image.dart';
import 'package:path/path.dart' as path;
import 'package:pdfium_bindings/pdfium_bindings.dart';

void main() {
  final stopwatch = Stopwatch()..start();
  final libraryPath = path.join(Directory.current.path, 'pdfium.dll');
  final dylib = DynamicLibrary.open(libraryPath);
  final pdfium = PDFiumBindings(dylib);

  const allocate = calloc;

  final config = allocate<FPDF_LIBRARY_CONFIG>();
  config.ref.version = 2;
  config.ref.m_pUserFontPaths = nullptr;
  config.ref.m_pIsolate = nullptr;
  config.ref.m_v8EmbedderSlot = 0;
  pdfium.FPDF_InitLibraryWithConfig(config);

  final filePathP = stringToNativeInt8('1417.pdf');

  final doc = pdfium.FPDF_LoadDocument(filePathP, nullptr);
  if (doc == nullptr) {
    final err = pdfium.FPDF_GetLastError();
    throw PdfiumException.fromErrorCode(err);
  }

  final pageCount = pdfium.FPDF_GetPageCount(doc);
  print('pageCount: $pageCount');

  final page = pdfium.FPDF_LoadPage(doc, 0);
  if (page == nullptr) {
    final err = pdfium.getLastErrorMessage();
    pdfium.FPDF_CloseDocument(doc);
    throw PageException(message: err);
  }

  const scale = 1;
  final width = (pdfium.FPDF_GetPageWidth(page) * scale).round();
  final height = (pdfium.FPDF_GetPageHeight(page) * scale).round();

  print('page Width: $width');
  print('page Height: $height');

  // var backgroundStr = "FFFFFFFF"; // as int 268435455
  const background = 268435455;
  const startX = 0;
  final sizeX = width;
  const startY = 0;
  final sizeY = height;
  const rotate = 0;

  // 创建一个空位图并将页面渲染到该位图上
  // 位图始终使用每像素4字节。第一个字节总是双字对齐。
  // 字节顺序为BGRx(如果无alpha通道,则最后一个字节未使用)或BGRA。
  // 标志:FPDF_ANNOT | FPDF_LCD_TEXT

  final bitmap = pdfium.FPDFBitmap_Create(width, height, 0);
  pdfium.FPDFBitmap_FillRect(bitmap, 0, 0, width, height, background);
  pdfium.FPDF_RenderPageBitmap(
      bitmap, page, startX, startY, sizeX, sizeY, rotate, 0,);
  // 位图缓冲区的第一个字节指针,数据以BGRA格式存储
  final pointer = pdfium.FPDFBitmap_GetBuffer(bitmap);
  // stride = 宽度 * 每像素4字节 BGRA
  // var stride = pdfium.FPDFBitmap_GetStride(bitmap);
  // print('stride $stride');

  final Image image = Image.fromBytes(
      width: width,
      height: height,
      bytes: pointer.asTypedList(width * height * 4).buffer,
      order: ChannelOrder.bgra,
      numChannels: 4,
  );

  // 将位图保存为PNG文件
  File('out.png').writeAsBytesSync(encodePng(image));

  // 清理
  pdfium.FPDFBitmap_Destroy(bitmap);

  pdfium.FPDF_ClosePage(page);
  allocate.free(filePathP);

  pdfium.FPDF_DestroyLibrary();
  allocate.free(config);

  print('end: ${stopwatch.elapsed}');
}

高级示例

示例 1

import 'package:pdfium_bindings/pdfium_bindings.dart';

void main() {
  PdfiumWrap()
      .loadDocumentFromPath('1417.pdf')
      .loadPage(0)
      .savePageAsPng('out.png')
      .closePage()
      .closeDocument()
      .dispose();
}

示例 2

import 'package:http/http.dart' as http;
import 'package:pdfium_bindings/pdfium_bindings.dart';

void main() async {
  final pdfium = PdfiumWrap();

  final resp = await http.get(
    Uri.parse(
      'https://www.riodasostras.rj.gov.br/wp-content/uploads/2022/03/1426.pdf',
    ),
  );
  final bytes = resp.bodyBytes;

  pdfium
      .loadDocumentFromBytes(bytes)
      .loadPage(0)
      .savePageAsJpg('out.jpg', qualityJpg: 80)
      .closePage()
      .closeDocument()
      .dispose();
}

更多关于Flutter PDF渲染插件pdfium_bindings的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter PDF渲染插件pdfium_bindings的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用pdfium_bindings插件来渲染PDF文件的示例代码。pdfium_bindings是一个较底层的PDF渲染库,它允许你在Flutter应用中直接操作PDF文档。请注意,pdfium_bindings需要一些原生代码的集成,因此在使用之前,请确保你已经熟悉如何在Flutter项目中添加和配置原生依赖。

首先,你需要在pubspec.yaml文件中添加pdfium_bindings依赖:

dependencies:
  flutter:
    sdk: flutter
  pdfium_bindings: ^x.y.z  # 请替换为最新版本号

然后,执行flutter pub get来获取依赖。

接下来,你需要在iOS和Android平台上进行一些配置。

iOS配置

  1. 打开ios/Runner/Info.plist,并添加以下权限请求(如果需要从文件系统中加载PDF):
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
  1. 确保在ios/Podfile中启用了Swift支持(如果尚未启用):
platform :ios, '10.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try running flutter pub get in your flutter project."
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '5.0'  # 或者更高版本
    end
  end
end

Android配置

通常,pdfium_bindings在Android上的配置较为简单,因为它主要依赖于CMake或ndk-build来构建原生库。你可能需要在android/app/build.gradle中确保NDK支持已启用:

android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++17"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    ...
}

并创建一个简单的CMakeLists.txt文件(如果尚未存在):

cmake_minimum_required(VERSION 3.4.1)

add_library(pdfium SHARED IMPORTED)
set_target_properties(pdfium PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/path/to/libpdfium.so)

find_library(log-lib log)

target_link_libraries(your_target_name pdfium ${log-lib})

请注意,这里的path/to/libpdfium.so需要替换为实际的PDFium库路径。

Flutter代码示例

下面是一个简单的Flutter代码示例,展示如何使用pdfium_bindings来渲染PDF页面:

import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:pdfium_bindings/pdfium_bindings.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  PdfiumController? _pdfiumController;
  Uint8List? _pdfData;

  @override
  void initState() {
    super.initState();
    // 这里加载你的PDF文件数据,例如从资产文件或网络加载
    _loadPdf();
  }

  Future<void> _loadPdf() async {
    // 示例:从资产文件加载PDF数据
    ByteData data = await rootBundle.load('assets/sample.pdf');
    _pdfData = data.buffer.asUint8List();

    // 初始化PdfiumController
    _pdfiumController = PdfiumController(
      document: PdfiumDocument.fromData(_pdfData!),
      password: '', // 如果PDF有密码,则在这里提供
    );

    // 渲染第一页
    _pdfiumController!.jumpToPage(1);

    // 设置页面大小(可选)
    _pdfiumController!.setPageSize(Size(double.infinity, double.infinity));

    // 监听页面渲染完成事件
    _pdfiumController!.pageFinished.listen((pageIndex) {
      print('Page $pageIndex finished rendering.');
    });

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('PDFium Demo'),
        ),
        body: _pdfiumController != null
            ? PdfiumView(
                controller: _pdfiumController!,
              )
            : Center(
                child: CircularProgressIndicator(),
              ),
      ),
    );
  }

  @override
  void dispose() {
    _pdfiumController?.dispose();
    super.dispose();
  }
}

请注意,上述代码是一个简化的示例,实际应用中你可能需要处理更多的错误情况,并根据需要调整页面渲染和交互逻辑。此外,pdfium_bindings库的API可能会随着版本更新而发生变化,因此请参考最新的官方文档和示例代码。

回到顶部