This document explains how to access low-level PDFium bindings directly when working with the pdfrx_engine package.
While pdfrx_engine provides high-level Dart APIs for PDF operations, you may occasionally need direct access to PDFium's native functions. The low-level PDFium bindings are provided by the pdfium_dart package:
package:pdfium_dart/pdfium_dart.dart- Raw FFI bindings generated from PDFium headers
import 'package:pdfium_dart/pdfium_dart.dart';This import provides access to the auto-generated FFI bindings that directly map to PDFium's C API. These bindings are generated using ffigen from PDFium headers and include all PDFium functions with their original names (e.g., FPDF_InitLibrary, FPDF_LoadDocument, etc.).
The pdfium_dart package provides:
- The
PDFiumclass for accessing PDFium functions - Auto-generated FFI bindings for all PDFium C API functions
getPdfium()function for resolving the PDFium library for the current Dart or Flutter runtime
PDFium must be initialized before use. The high-level API handles this automatically, but when using raw bindings directly, you may need to ensure initialization.
There are basically three ways to initialize PDFium:
import 'package:pdfium_dart/pdfium_dart.dart';
import 'dart:ffi';
// Load PDFium library manually
final pdfium = PDFium(DynamicLibrary.open('path/to/libpdfium.so'));
pdfium.FPDF_InitLibrary(); // or pdfium.FPDF_InitLibraryWithConfig(...)import 'package:pdfium_dart/pdfium_dart.dart';
Future<void> initializePdfium() async {
// Resolves PDFium for the current Dart or Flutter runtime.
final pdfium = getPdfium();
pdfium.FPDF_InitLibrary(); // or pdfium.FPDF_InitLibraryWithConfig(...)
}getPdfium() uses explicit module paths first, resolves Flutter-packaged PDFium where applicable, and falls back to the native asset produced by the build hook.
import 'package:pdfrx/pdfrx.dart';
pdfrxFlutterInitialize();import 'package:pdfrx_engine/pdfrx_engine.dart';
await pdfrxInitialize();For more information about initialization, see pdfrx Initialization.
Important: pdfrx does not support unloading PDFium. Never call FPDF_DestroyLibrary as it will cause undefined behavior. PDFium remains loaded for the lifetime of the application.
import 'package:pdfium_dart/pdfium_dart.dart';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
void example() async {
// Resolve PDFium bindings for the current runtime.
final pdfium = getPdfium();
// Use arena to automatically manage memory
using((arena) {
// Access PDFium functions
final doc = pdfium.FPDF_LoadDocument(
'path/to/file.pdf'.toNativeUtf8(allocator: arena).cast<Char>(),
nullptr,
);
if (doc != nullptr) {
final pageCount = pdfium.FPDF_GetPageCount(doc);
print('Page count: $pageCount');
// Don't forget to clean up
pdfium.FPDF_CloseDocument(doc);
}
});
}import 'package:pdfium_dart/pdfium_dart.dart';
void workWithPage(PDFium pdfium, FPDF_DOCUMENT doc) {
final page = pdfium.FPDF_LoadPage(doc, 0); // Load first page
if (page != nullptr) {
final width = pdfium.FPDF_GetPageWidth(page);
final height = pdfium.FPDF_GetPageHeight(page);
print('Page dimensions: ${width}x${height}');
pdfium.FPDF_ClosePage(page);
}
}When working with raw bindings, you're responsible for proper memory management. Always use Arena to ensure allocated memory is properly released:
import 'package:pdfium_dart/pdfium_dart.dart';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
void memoryExample() {
final pdfium = getPdfium();
// Use arena for automatic memory management
using((arena) {
final pathPtr = 'path/to/file.pdf'.toNativeUtf8(allocator: arena);
final doc = pdfium.FPDF_LoadDocument(
pathPtr.cast<Char>(),
nullptr,
);
if (doc != nullptr) {
// Use document...
pdfium.FPDF_CloseDocument(doc);
}
// Memory allocated by arena is automatically freed when the using block ends
});
}
// Alternative: Manual memory management (not recommended)
void manualMemoryExample() {
final pdfium = getPdfium();
final pathPtr = 'path/to/file.pdf'.toNativeUtf8();
try {
final doc = pdfium.FPDF_LoadDocument(
pathPtr.cast<Char>(),
nullptr,
);
if (doc != nullptr) {
// Use document...
pdfium.FPDF_CloseDocument(doc);
}
} finally {
// Must manually free allocated memory
calloc.free(pathPtr);
}
}PDFium is not thread-safe. Ensure all PDFium calls are made from the same thread, typically the main isolate.
Always check return values from PDFium functions:
final pdfium = getPdfium();
using((arena) {
final pathPtr = 'path/to/file.pdf'.toNativeUtf8(allocator: arena);
final doc = pdfium.FPDF_LoadDocument(pathPtr.cast<Char>(), nullptr);
if (doc == nullptr) {
final error = pdfium.FPDF_GetLastError();
print('Failed to load document. Error code: $error');
// Handle error...
} else {
// Use document...
pdfium.FPDF_CloseDocument(doc);
}
});Consider using low-level bindings when:
- You need functionality not exposed by the high-level API
- You're implementing performance-critical operations
- You need fine-grained control over memory management
- You're extending pdfrx_engine with new features
Using low-level bindings bypasses many safety checks and conveniences provided by the high-level API. Ensure you:
- Properly manage memory allocation and deallocation
- Handle errors appropriately
- Follow PDFium's threading requirements
- Test thoroughly on all target platforms