Open and manipulate PDF documents without displaying a viewer. This is useful for batch processing, annotation copying, and server-side operations.
// Open a document without displaying a viewer
final document = await Nutrient.openDocument('/path/to/document.pdf');
try {
// Perform operations...
final pageCount = await document.getPageCount();
final annotations = await document.getAnnotations(0, AnnotationType.all);
// Save changes
await document.save();
} finally {
// Always close headless documents to release resources
await document.close();
}Basic:
final document = await Nutrient.openDocument('/path/to/document.pdf');Password-protected:
final document = await Nutrient.openDocument(
'/path/to/encrypted.pdf',
password: 'secret',
);From assets (extract first):
// Copy asset to temporary directory
final bytes = await rootBundle.load('assets/document.pdf');
final tempDir = await Nutrient.getTemporaryDirectory();
final tempFile = File('${tempDir.path}/document.pdf');
await tempFile.writeAsBytes(bytes.buffer.asUint8List());
final document = await Nutrient.openDocument(tempFile.path);Get annotations from a page:
// Get all annotations on page 0
final annotations = await document.getAnnotations(0, AnnotationType.all);
// Get specific annotation types
final inkAnnotations = await document.getAnnotations(0, AnnotationType.ink);
final highlights = await document.getAnnotations(0, AnnotationType.highlight);Iterate through all pages:
final pageCount = await document.getPageCount();
final allAnnotations = <Annotation>[];
for (int i = 0; i < pageCount; i++) {
final pageAnnotations = await document.getAnnotations(i, AnnotationType.all);
allAnnotations.addAll(pageAnnotations);
}
print('Found ${allAnnotations.length} annotations');Add a single annotation:
final inkAnnotation = InkAnnotation(
pageIndex: 0,
bbox: [100, 100, 300, 200],
lines: InkLines(points: [
[100, 150],
[150, 100],
[200, 150],
[250, 100],
[300, 150],
]),
lineWidth: 3,
strokeColor: Colors.blue,
createdAt: DateTime.now().toIso8601String(),
);
await document.addAnnotation(inkAnnotation);Add multiple annotations:
final annotations = [
NoteAnnotation(
pageIndex: 0,
bbox: [50, 50, 80, 80],
contents: 'This is a note',
createdAt: DateTime.now().toIso8601String(),
),
HighlightAnnotation(
pageIndex: 0,
bbox: [100, 200, 400, 220],
rects: [[100, 200, 400, 220]],
createdAt: DateTime.now().toIso8601String(),
),
];
await document.addAnnotations(annotations);// Get an annotation first
final annotations = await document.getAnnotations(0, AnnotationType.ink);
if (annotations.isNotEmpty) {
await document.removeAnnotation(annotations.first);
}Process annotations to flatten, embed, or remove them from the document.
Flatten all annotations:
await document.processAnnotations(
AnnotationType.all,
AnnotationProcessingMode.flatten,
'/path/to/flattened.pdf',
);Remove specific annotation types:
// Remove all ink annotations
await document.processAnnotations(
AnnotationType.ink,
AnnotationProcessingMode.remove,
'/path/to/output.pdf',
);Embed annotations:
await document.processAnnotations(
AnnotationType.all,
AnnotationProcessingMode.embed,
'/path/to/embedded.pdf',
);Copy annotations from one document to another using XFDF export/import.
final sourceDoc = await Nutrient.openDocument('/path/to/source.pdf');
final targetDoc = await Nutrient.openDocument('/path/to/target.pdf');
try {
// Export annotations from source as XFDF
final tempDir = await Nutrient.getTemporaryDirectory();
final xfdfPath = '${tempDir.path}/annotations.xfdf';
await sourceDoc.exportXfdf(xfdfPath);
// Import annotations to target
final xfdfContent = await File(xfdfPath).readAsString();
await targetDoc.importXfdf(xfdfContent);
// Save target document
await targetDoc.save(outputPath: '/path/to/output.pdf');
// Cleanup
await File(xfdfPath).delete();
} finally {
await sourceDoc.close();
await targetDoc.close();
}Alternative: Using Instant JSON:
final sourceDoc = await Nutrient.openDocument('/path/to/source.pdf');
final targetDoc = await Nutrient.openDocument('/path/to/target.pdf');
try {
// Export annotations as Instant JSON
final instantJson = await sourceDoc.exportInstantJson();
if (instantJson != null) {
// Apply to target document
await targetDoc.applyInstantJson(instantJson);
await targetDoc.save();
}
} finally {
await sourceDoc.close();
await targetDoc.close();
}Get form field value:
final value = await document.getFormFieldValue('form_field_name');Set form field value:
await document.setFormFieldValue('New Value', 'form_field_name');Get all form fields:
final formFields = await document.getFormFields();
for (final field in formFields) {
print('${field.name}: ${field.value}');
}Save to original location:
await document.save();Save to new location:
await document.save(outputPath: '/path/to/output.pdf');Save with options:
await document.save(
outputPath: '/path/to/output.pdf',
options: DocumentSaveOptions(
incremental: true,
flatten: false,
),
);Export as bytes:
final pdfBytes = await document.exportPdf();
await File('/path/to/output.pdf').writeAsBytes(pdfBytes);Get page count:
final pageCount = await document.getPageCount();Get page info:
final pageInfo = await document.getPageInfo(0);
print('Page size: ${pageInfo.width} x ${pageInfo.height}');
print('Rotation: ${pageInfo.rotation}');try {
final document = await Nutrient.openDocument('/path/to/document.pdf');
try {
// Operations that might fail
await document.processAnnotations(
AnnotationType.all,
AnnotationProcessingMode.flatten,
'/path/to/output.pdf',
);
} catch (e) {
print('Processing failed: $e');
} finally {
await document.close();
}
} catch (e) {
print('Failed to open document: $e');
}A common use case is copying annotations from a watermarked document to a clean document.
Future<void> removeWatermarkKeepAnnotations({
required String watermarkedPath,
required String cleanPath,
required String outputPath,
}) async {
final watermarkedDoc = await Nutrient.openDocument(watermarkedPath);
final cleanDoc = await Nutrient.openDocument(cleanPath);
try {
// Export annotations from watermarked document
final tempDir = await Nutrient.getTemporaryDirectory();
final xfdfPath = '${tempDir.path}/annotations_transfer.xfdf';
await watermarkedDoc.exportXfdf(xfdfPath);
// Import to clean document
final xfdfContent = await File(xfdfPath).readAsString();
await cleanDoc.importXfdf(xfdfContent);
// Save result
await cleanDoc.save(outputPath: outputPath);
// Cleanup
await File(xfdfPath).delete();
print('Successfully created clean document with annotations at: $outputPath');
} finally {
await watermarkedDoc.close();
await cleanDoc.close();
}
}| Feature | Android | iOS | Web |
|---|---|---|---|
| Open document | Yes | Yes | Yes |
| Read annotations | Yes | Yes | Yes |
| Add annotations | Yes | Yes | Yes |
| Remove annotations | Yes | Yes | Yes |
| Process annotations | Yes | Yes | No |
| Export XFDF | Yes | Yes | Yes |
| Import XFDF | Yes | Yes | Yes |
| Export Instant JSON | Yes | Yes | Yes |
| Apply Instant JSON | Yes | Yes | Yes |
| Form fields | Yes | Yes | Yes |
| Save document | Yes | Yes | Yes |
| Password protection | Yes | Yes | Yes |
-
Always close documents - Headless documents hold native resources that must be released.
-
Use try-finally - Ensure documents are closed even when errors occur.
-
Prefer XFDF for annotation transfer - More reliable across different document versions.
-
Check for null - Some operations may return null if they fail.
-
Handle errors gracefully - Document operations can fail for various reasons (file not found, permissions, corrupted files).