dart_pdf_editor 3.2.0
dart_pdf_editor: ^3.2.0 copied to clipboard
Flutter PDF viewer and editor for Dart: native rendering, text selection, search, annotations, forms, signatures, page editing, and document comparison.

dart_pdf_editor #
A Flutter PDF viewer and editor rendered natively in Dart, with no platform views or native PDF libraries. The same code runs on iOS, Android, macOS, Windows, Linux, and the web.

Install #
flutter pub add dart_pdf_editor
Two drop-in widgets carry the whole UI. Give them bytes and bounded space; everything in the screenshot above (search, page navigation, panels, tools, undo/redo, save) is wired up:
import 'package:dart_pdf_editor/dart_pdf_editor.dart';
// A complete PDF editor
PdfEditorView(
bytes: pdfBytes,
onSave: (bytes) => /* write the file */,
)
// A view-only reader
PdfReader(bytes: pdfBytes)
Both follow the ambient Material Theme (dark mode included), persist
user preferences on the device, and pare down with feature flags:
PdfEditorView(
bytes: pdfBytes,
features: const PdfEditorFeatures(
propertiesPanel: false,
flatten: false,
tools: {PdfEditTool.select, PdfEditTool.ink, PdfEditTool.freeText},
),
toolbarTrailing: [
(context, editing, viewer) => IconButton(
icon: const Icon(Icons.cloud_upload_outlined),
tooltip: 'Publish',
onPressed: () => publish(editing.bytes),
),
],
)
For fully custom editor chrome, replace the stock toolbar and drive the controller directly:
PdfEditorView(
bytes: pdfBytes,
toolbarBuilder: (context, editing, viewer) => BottomAppBar(
child: IconButton(
icon: const Icon(Icons.crop_square),
tooltip: 'Rectangle',
onPressed: () => editing.tool = PdfEditTool.rectangle,
),
),
)
Try the live demo of the example app on Flutter web, with a built-in feature showcase document.
Built on the pure-Dart
dart-pdf suite: pdf_cos
(file syntax) ← pdf_document (document semantics + editing) ←
pdf_graphics (interpreter + fonts) ← dart_pdf_editor (Flutter widgets).
Optional bundled assets #
The editor's six bundled fonts and its web render worker (~1.8 MB package
download) ship in a separate opt-in package,
dart_pdf_editor_assets, rather than in
dart_pdf_editor itself. Flutter bundles a package's declared assets on every
build target, so keeping them out of the always-depended-on package is the only
way to let a viewer-only app avoid downloading and installing them.
To get the historical full-featured editor, add the package and register it once at startup, before opening a viewer:
flutter pub add dart_pdf_editor dart_pdf_editor_assets
import 'package:dart_pdf_editor_assets/dart_pdf_editor_assets.dart';
void main() {
registerBundledEditorAssets(); // bundled fonts + web worker
runApp(const MyApp());
}
Pick the tier that fits your build:
| Tier | Depend on | Register | Bundled fonts + web worker |
|---|---|---|---|
| Viewer / editor, size-minimal | dart_pdf_editor |
– | not shipped |
| + Off-main-thread web rendering | both | registerBundledEditorAssets(fonts: false) |
worker only |
| Full editor, app-provided fonts | dart_pdf_editor |
assign pdfBundledFonts yourself |
your fonts |
| Full editor (historical default) | both | registerBundledEditorAssets() |
both |
What each optional group powers - everything else works without them:
| Capability | Needs |
|---|---|
| Font menu's "bundled" group (DejaVu, Fira Sans, Spectral, Lobster) | bundled fonts |
| Fallback glyphs when editing composite (/Type0) text a subset font can't draw | bundled fonts (DejaVu trio) |
| Off-main-thread page rendering on web | web worker |
Missing assets degrade gracefully: the font menu simply drops the bundled group
(base-14, document, platform and custom fonts still work), composite-text
fallback is skipped, and web rendering falls back to the main thread. An app can
also supply its own catalogue - set pdfBundledFonts to your own
PdfBundledFonts (each backed by an asset key or a loadBytes byte loader).
Performance #
On a real-world corpus (49 files / 255 pages of CAD drawings, scans, reports, and forms), the pure-Dart page interpreter processes 104.5 pages/s at scale 2: 9.6 ms/page, compared with PDFium rasterizing at 23.1 ms/page. The apples-to-apples full Flutter render runs at 45.2 ms/page, including image decoding, rasterization, and readback.
| engine | ms/page | vs PDFium |
|---|---|---|
| dart-pdf interpret (pure Dart, no raster) | 9.6 | 2.42× faster |
| PDFium rasterize (document open excluded) | 23.1 | 1.00× |
| dart-pdf render (full Flutter raster + readback) | 45.2 | 1.95× slower |
Numbers and methodology are in
benchmark/.
The harnesses diff dart-pdf against PDFium file by file. These figures measure
static page throughput, not scrolling FPS or dropped frames; the real-Chrome
interaction harness reports those separately. In five interleaved
scroll-plan runs, 3.1.1 reduced median over-budget frame events from 7 to 1
versus 3.1.0 (-85.7%).
The drop-in shells use adaptive performance tuning by default. Auto selects a platform-, core-, and document-aware worker count, then adjusts safe preview and image-resolution knobs from observed render latency, result sizes, and frame jank. Use a controller to inspect it or choose a fixed configuration:
final performance = PdfPerformanceController(); // Auto
PdfReader(bytes: pdfBytes, performance: performance);
debugPrint('${performance.diagnostics}');
// Applied when the document worker next starts; never resized mid-scroll.
performance.mode = const PdfPerformanceMode.fixed(workerCount: 2);
Viewing #
- Zooming/panning viewer with fit-page and fit-width modes, deep-zoom detail rendering past the raster caps, and exact scroll metrics on long mixed-size documents.
- Smooth fast scrolling on heavy documents: pages flying past show low-res previews (filled in by a background prerender) instead of blank paper, and full rendering resumes the moment scrolling settles.
- Progressive rendering records pages in a default worker, streams partial records as they are produced, and reveals complex pages top-down.
- Text selection (mouse, and touch with selection handles), full-text search with page-text and annotation-content results, link navigation, outlines.
- Faithful print-oriented overprint and spot-color rendering, including colorants sampled from images.
- Theming via
PdfViewerTheme, dark mode, arbitrary page colors, and a hide-all-annotations toggle.
Editing #
Every edit is an incremental save: undo/redo is built in, and revisions are byte prefixes of one buffer.
- Annotation tools: highlight/underline/strikeout/squiggly, ink with stylus pressure and spline smoothing, shapes, free text with in-place editing, notes, stamps (including custom saved stamps), and a saved ink signature. The hyperlink tool authors URI and in-document links, and placed images and raster snapshots can be cropped interactively.
- Certificate-backed digital signatures: load an in-memory RSA private key and X.509 chain, then add a validated PAdES B-B signature as an undoable document revision. This is separate from the drawn ink-signature tool.
- True redaction: place
/Redactmarks, then burn them per §12.5.6.23 — covered text and images are removed from the file bytes with a compacted save (not painted over), so the redacted content is unrecoverable. - Direct manipulation: select (single, marquee, ⌘A), move, resize, and rotate with live appearance previews, plus a slicing circle eraser, copy/cut/paste, z-order, restyling, and a context menu with host-extensible entries (right-click, or long-press on touch).
- Lock/unlock annotations with Acrobat/Bluebeam-compatible PDF flags and assign custom keyboard shortcuts to every editing tool.
- Forms: fill text/checkbox/radio/choice fields in place, set button
images, and administer fields (add, rename, retype, delete, flatten).
Fields are highlighted with a translucent wash by default
(
PdfViewer.highlightFormFields). - OCR seam:
PdfOcrEngineplusPdfEditor.applyOcrrasterizes a page, runs any recognizer you provide, and injects an invisible selectable text layer. Usepdf_ocr_ondevicefor native offline OCR, orpdf_ocr_vlmfor HTTP OCR services and Flutter web. - Panels: thumbnail sidebar with drag-reorder, annotation sidebar with search and multi-select, properties panel, and search results panel, all resizable and persisted.
- Permissions:
/Fread-only and locked flags are honored, and acanEditAnnotationpredicate implements policies like "users may only edit their own annotations" in one line. - Sync: an
annotationChangesfeed plusapplyRemoteChangefor wiring annotations to a collaborative store (Firestore, websockets, etc.). A remote apply is a non-crossable undo checkpoint; later local edits remain undoable without removing the remote state.
Composing your own UI #
PdfEditorView and PdfReader are assembled from public parts:
PdfViewer, PdfEditingController, PdfEditingToolbar, and the panels,
so apps wanting custom chrome can wire those directly:
import 'package:pdf_document/pdf_document.dart';
import 'package:dart_pdf_editor/dart_pdf_editor.dart';
// Just the viewer
PdfViewer(document: PdfDocument.open(bytes));
// Your own editor layout. The controller owns the document revisions, and
// the viewer reads the current one from it and follows its edits itself -
// no need to pass `document` or rebuild the viewer as revisions land.
final editing = PdfEditingController(bytes);
final viewer = PdfViewerController();
Column(children: [
Expanded(
child: PdfViewer(
controller: viewer,
editing: editing,
),
),
PdfEditingToolbar(controller: editing, viewerController: viewer),
]);
// Saving
final Uint8List saved = editing.bytes;
// Cryptographically sign with PEM/DER key and certificate files.
final identity = PdfDigitalSignatureIdentity.fromFiles(
privateKey: privateKeyBytes,
certificates: certificateFileBytes,
);
await editing.addDigitalSignature(
identity,
reason: 'Approved',
location: 'Melbourne',
);
final Uint8List signed = editing.bytes; // PAdES B-B, validated on commit
The example app is a thin shell over PdfEditorView (with a
toggle that swaps in PdfReader) plus the app-side concerns: file
open/save dialogs, theme mode, and Flutter overlays pinned onto PDF
pages. It runs on all six platforms.
OCR #
dart_pdf_editor owns the PDF side of OCR: it renders a page image,
hands it to a PdfOcrEngine, and writes the returned text boxes back as
invisible text. It deliberately does not bundle a recognizer in the core
viewer package.
For native offline OCR:
flutter pub add pdf_ocr_ondevice
import 'dart:typed_data';
import 'package:dart_pdf_editor/dart_pdf_editor.dart';
import 'package:pdf_document/pdf_document.dart';
import 'package:pdf_ocr_ondevice/pdf_ocr_ondevice.dart';
Future<Uint8List> addOcrNative(Uint8List bytes) async {
if (!PdfOcrModelManager.isSupported) return bytes;
final manager = PdfOcrModelManager();
final model = PdfOcrModels.ppOcrV5Mobile;
if (!await manager.isDownloaded(model)) {
await manager.download(model);
}
final engine = await OnDeviceOcrEngine.fromDownloadedModel(manager, model);
try {
final editor = PdfEditor(PdfDocument.open(bytes));
for (var page = 0; page < editor.document.pageCount; page++) {
await editor.applyOcr(page, engine, pixelRatio: 2);
}
return editor.save();
} finally {
await engine.dispose();
manager.close();
}
}
For web or server-backed OCR:
flutter pub add pdf_ocr_vlm
import 'dart:typed_data';
import 'package:dart_pdf_editor/dart_pdf_editor.dart';
import 'package:pdf_document/pdf_document.dart';
import 'package:pdf_ocr_vlm/pdf_ocr_vlm.dart';
Future<Uint8List> addOcrViaHttp(Uint8List bytes) async {
final engine = VlmOcrEngine(
endpoint: Uri.parse('https://ocr.example.com/ocr'),
minConfidence: 0.3,
);
try {
final editor = PdfEditor(PdfDocument.open(bytes));
for (var page = 0; page < editor.document.pageCount; page++) {
await editor.applyOcr(page, engine, pixelRatio: 2.5);
}
return editor.save();
} finally {
engine.close();
}
}
After saving, reopen or replace the document bytes in PdfReader /
PdfEditorView. The layer is invisible by default, so the scan looks the
same, but text selection, search, copy, and extraction work. Pass
visible: true to applyOcr while debugging box alignment.
Web rendering #
On the web, PdfReader/PdfEditorView can run page interpretation and image
decode off the browser main thread in a Web Worker. The worker script ships
in the optional dart_pdf_editor_assets package;
depend on it and call registerBundledEditorAssets() once at startup to enable
off-main-thread rendering with no further setup (see
Optional bundled assets). Without it, web rendering
runs on the main thread. If the worker URL is set but the script cannot be
loaded, rendering degrades to the main thread too.
Apps that want to self-host the worker under their own URL can build a custom bundle from the app root and override the URL before opening a viewer:
dart run dart_pdf_editor:build_web_worker # writes web/pdf_render_worker.dart.js
pdfRenderWorkerScriptUrl = 'pdf_render_worker.dart.js';
Set pdfRenderWorkerScriptUrl = null to force main-thread rendering.
The worker does not require COOP/COEP headers, but a cross-origin isolated
host lets pooled workers share the document bytes through SharedArrayBuffer
instead of cloning them per worker. Full setup, dart2wasm-host notes, and the
worker protocol are in
doc/render_worker_web.md.
Under the hood #
Encrypted files (RC4/AES-128/AES-256, encrypt-on-write), digital
signature validation, the full shading and blend-mode set, ICC color,
CCITT/JBIG2/JPEG 2000 images, and lenient parsing of broken real-world
files, with conformance pinned against the Ghent Output Suite and the
PDF.js test corpus. Checked-in PDF.js visual comparisons are available at
../../test_corpora/pdfjs/_renders/README.md.