pdf_stamp_editor 0.6.0
pdf_stamp_editor: ^0.6.0 copied to clipboard
PDF viewer with stamp overlays and native export layer (Android+iOS supported).
pdf_stamp_editor #
A Flutter package for viewing PDFs with stamp overlays and exporting stamped PDFs on mobile platforms.
Features #
- 📄 PDF Viewing: Display PDFs using the powerful
pdfrxviewer - 🖼️ Stamp Placement: Place and position image or text stamps on PDF pages
- ✏️ Interactive Editing: Drag, resize, rotate, and select stamps with gestures (including cross-page dragging)
- 💾 PDF Export: Export stamped PDFs with vector-based stamping (mobile only)
- 🎮 Programmatic Control: Full API for adding, updating, and managing stamps
- 🌐 Web Support: View and place stamps on web (export disabled)
- 🎨 Customizable: Support for both PNG image stamps and text stamps with configurable defaults and custom rendering
Platform Support #
- ✅ Mobile (iOS/Android): Full support including export
- ⚠️ Web: View and place stamps only (export not supported)
- ❌ Desktop (Windows/macOS/Linux): Not currently supported
Getting Started #
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
pdf_stamp_editor: ^0.5.0
Quick Start #
import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';
import 'package:pdf_stamp_editor/pdf_stamp_editor.dart';
// For loading files, you might also need:
// import 'package:file_picker/file_picker.dart';
// import 'package:path_provider/path_provider.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Required if you may touch pdfrx engine / PdfDocument APIs early.
pdfrxFlutterInitialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Load PDF bytes from file, network, or assets
// Example: final pdfBytes = await FilePicker.platform.pickFiles(...);
// Example: final pdfBytes = await http.get(...).then((r) => r.bodyBytes);
return MaterialApp(
home: PdfStampEditorPage(
pdfBytes: pdfBytes, // Uint8List from file/network/assets
pngBytes: pngBytes, // Optional: Uint8List PNG image for stamp placement
),
);
}
}
Usage #
Basic Stamp Placement #
Add stamps by tapping on the PDF. The behavior depends on the mode parameter (default is image):
- Tap: Places a stamp (image or text) at the tapped location
- Long Press: Disabled by default (can be enabled via
enableLongPress: true)
PdfStampEditorPage(
pdfBytes: pdfBytes,
pngBytes: pngBytes,
mode: StampEditorMode.image, // Default: image. Options: none, text, image
onStampsChanged: (stamps) {
print('Stamps count: ${stamps.length}');
},
)
Configuration #
Customize stamp behavior using configuration classes:
PdfStampEditorPage(
pdfBytes: pdfBytes,
pngBytes: pngBytes,
// Configure text stamps
textStampConfig: const TextStampConfig(
text: 'CONFIDENTIAL',
fontSizePt: 24,
color: Colors.blue,
fontWeight: FontWeight.bold,
),
// Or disable text stamps entirely:
// textStampConfig: const TextStampConfig.disabled(),
// Configure image stamps
imageStampConfig: const ImageStampConfig(
widthPt: 200,
heightPt: null, // null = compute from image aspect ratio
maintainAspectRatio: true, // Use actual image dimensions
),
// Or use explicit dimensions:
// imageStampConfig: const ImageStampConfig.explicit(
// widthPt: 200,
// heightPt: 100,
// ),
// Configure selection styling
selectionConfig: const SelectionConfig(
borderColor: Colors.green,
borderWidth: 3.0,
),
// Configure web PDF viewer source name
webSourceName: 'my-document.pdf',
)
Configuration Options:
mode: Control tap behavior (none,text, orimage)enableLongPress: Enable/disable adding text stamps via long-press (default:false)TextStampConfig: Customize text stamp text, font size, color, and weightImageStampConfig: Configure image stamp dimensions and aspect ratio behavior- When
maintainAspectRatio: trueandheightPt: null, height is automatically computed from image dimensions
- When
SelectionConfig: Customize selection border and delete button appearancewebSourceName: Set the source name for the web PDF viewer
Interactive Editing #
Enable drag, resize, rotate, and selection gestures:
PdfStampEditorPage(
pdfBytes: pdfBytes,
enableDrag: true, // Enable drag gestures (default: false)
enableResize: true, // Enable pinch-to-resize (default: true)
enableRotate: true, // Enable rotation gestures (default: true)
enableSelection: true, // Enable tap-to-select (default: true)
)
Gestures:
- Drag: Tap and hold a stamp, then drag to move it (requires
enableDrag: trueand acontroller). Stamps can be dragged seamlessly across different PDF pages. - Resize: Pinch to zoom on a stamp to resize it
- Rotate: Use rotation gesture (two fingers) on a stamp to rotate it
- Select: Tap a stamp to select it (shows border with configurable color and width)
- Delete: Tap the delete button on selected stamps, use controller buttons, or press Backspace/Delete key
Programmatic Control #
Use PdfStampEditorController for programmatic stamp management:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:pdf_stamp_editor/pdf_stamp_editor.dart';
class MyEditorPage extends StatefulWidget {
@override
State<MyEditorPage> createState() => _MyEditorPageState();
}
class _MyEditorPageState extends State<MyEditorPage> {
late final PdfStampEditorController controller;
Uint8List? pngBytes; // Load from file picker, network, or assets
@override
void initState() {
super.initState();
controller = PdfStampEditorController();
controller.addListener(() {
print('Stamps changed: ${controller.stamps.length}');
});
// Load PNG bytes (e.g., from file picker or assets)
// _loadPngBytes();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
void _addStamp() {
if (pngBytes == null) return; // Ensure PNG is loaded
final stamp = ImageStamp(
pageIndex: 0,
centerXPt: 200.0,
centerYPt: 300.0,
rotationDeg: 0.0,
pngBytes: pngBytes!,
widthPt: 100.0,
heightPt: 50.0,
);
controller.addStamp(stamp);
}
@override
Widget build(BuildContext context) {
return PdfStampEditorPage(
pdfBytes: pdfBytes,
controller: controller,
enableDrag: true,
enableResize: true,
enableRotate: true,
enableSelection: true,
);
}
}
Controller Methods:
addStamp(stamp)- Add a stamp programmaticallyupdateStamp(index, stamp)- Update stamp at indexremoveStamp(index)- Remove stamp at indexclearStamps()- Remove all stampsselectStamp(index, {toggle})- Select/deselect stampclearSelection()- Clear all selectionsdeleteSelectedStamps()- Delete all selected stampsisSelected(index)- Check if stamp is selected
Exporting PDFs #
Export stamped PDFs on mobile platforms (Android/iOS):
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:pdf_stamp_editor/pdf_stamp_editor.dart';
Future<void> exportPdf(Uint8List pdfBytes, List<PdfStamp> stamps) async {
try {
final outBytes = await PdfStampEditorExporter.applyStamps(
inputPdfBytes: pdfBytes,
stamps: stamps, // List<PdfStamp> from PdfStampEditorPage
);
// Save or share outBytes
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/stamped.pdf');
await file.writeAsBytes(outBytes);
print('Exported to: ${file.path}');
} catch (e) {
print('Export failed: $e');
}
}
Note: Export requires FFI/PDFium and is only available on mobile platforms. The export functionality uses native implementations:
- Android: PdfBox-Android library
- iOS: PDFKit framework
Advanced Features #
Configuration #
Customize default stamp creation and styling behavior:
Text Stamp Configuration
// Customize text stamp defaults
textStampConfig: const TextStampConfig(
text: 'APPROVED', // Text to place on long press
fontSizePt: 18, // Font size in points
color: Colors.red, // Text color
fontWeight: FontWeight.bold, // Font weight
),
// Disable text stamp creation entirely
textStampConfig: const TextStampConfig.disabled(),
Image Stamp Configuration
// Auto-compute height from image aspect ratio (recommended)
imageStampConfig: const ImageStampConfig(
widthPt: 200,
heightPt: null, // null = compute from image
maintainAspectRatio: true, // Use actual image dimensions
),
// Use explicit width and height
imageStampConfig: const ImageStampConfig.explicit(
widthPt: 200,
heightPt: 100,
),
// Use default aspect ratio (0.35) when maintainAspectRatio is false
imageStampConfig: const ImageStampConfig(
widthPt: 200,
heightPt: null,
maintainAspectRatio: false, // Use default ratio
),
When maintainAspectRatio is true and heightPt is null, the package automatically decodes the PNG image to get its actual dimensions and computes the height to maintain the aspect ratio. This ensures stamps always display with the correct proportions.
Selection Configuration
selectionConfig: const SelectionConfig(
borderColor: Colors.blue, // Border color for selected stamps
borderWidth: 2.0, // Border width in pixels
deleteButtonConfig: DeleteButtonConfig(
backgroundColor: Colors.red,
iconColor: Colors.white,
size: 28.0,
offsetX: 24.0,
offsetY: -24.0,
),
// Or disable delete button:
// deleteButtonConfig: DeleteButtonConfig.disabled(),
),
Callbacks #
Monitor stamp changes with callbacks:
PdfStampEditorPage(
pdfBytes: pdfBytes,
onStampsChanged: (stamps) {
// Called when stamps list changes (add/remove)
},
onStampSelected: (index, stamp) {
// Called when a stamp is selected
},
onStampUpdated: (index, stamp) {
// Called when a stamp is updated (drag/resize/rotate)
},
onStampDeleted: (indices) {
// Called when stamps are deleted
},
onTapDown: () {
// Called when tapping to place image stamp
},
onLongPressDown: () {
// Called when long-pressing to place text stamp
},
onImageStampPlaced: () {
// Called after image stamp is placed
},
)
Custom Stamp Rendering #
Customize stamp appearance with stampBuilder. The builder must return a Widget - if you want default rendering for some types, handle them explicitly or omit the stampBuilder parameter:
import 'dart:math' as math;
import 'package:pdf_stamp_editor/pdf_stamp_editor.dart';
import 'package:pdfrx/pdfrx.dart';
PdfStampEditorPage(
pdfBytes: pdfBytes,
stampBuilder: (context, stamp, page, scaledPageSize, position) {
if (stamp is ImageStamp) {
// Custom rendering for ImageStamp
final scale = PdfCoordinateConverter.pageScaleFactors(page, scaledPageSize);
final wPx = stamp.widthPt * scale.sx;
final hPx = stamp.heightPt * scale.sy;
return Container(
width: wPx,
height: hPx,
decoration: BoxDecoration(
border: Border.all(color: Colors.blue, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 4,
offset: Offset(2, 2),
),
],
),
child: Transform.rotate(
angle: stamp.rotationDeg * math.pi / 180,
child: Image.memory(stamp.pngBytes, fit: BoxFit.fill),
),
);
} else if (stamp is TextStamp) {
// Custom rendering for TextStamp
final scale = PdfCoordinateConverter.pageScaleFactors(page, scaledPageSize);
final fontPx = stamp.fontSizePt * scale.sy;
return Transform.rotate(
angle: stamp.rotationDeg * math.pi / 180,
child: Text(
stamp.text,
style: TextStyle(
fontSize: fontPx,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
);
}
// Handle all stamp types or omit stampBuilder to use defaults
return const SizedBox.shrink();
},
)
Coordinate Conversion #
Convert between PDF coordinates and screen coordinates for custom implementations. Note: PdfPoint and PdfPage are from the pdfrx package:
import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';
import 'package:pdf_stamp_editor/pdf_stamp_editor.dart';
// Convert screen coordinates to PDF coordinates
PdfPoint pdfPoint = PdfCoordinateConverter.viewerOffsetToPdfPoint(
page: pdfPage, // PdfPage from pdfrx
localOffsetTopLeft: Offset(100, 100), // Screen position
scaledPageSizePx: Size(612, 792), // Displayed page size
);
// Convert PDF coordinates to screen coordinates
Offset screenOffset = PdfCoordinateConverter.pdfPointToViewerOffset(
page: pdfPage,
xPt: 100.0, // PDF X coordinate (bottom-left origin)
yPt: 692.0, // PDF Y coordinate (bottom-left origin)
scaledPageSizePx: Size(612, 792),
);
// Get scale factors for a page
final scaleFactors = PdfCoordinateConverter.pageScaleFactors(
pdfPage,
Size(306, 396), // Scaled page size
);
All methods handle PDF page rotations (0°, 90°, 180°, 270°) correctly.
Example App #
See the example/ directory for a complete working example demonstrating:
- Basic stamp placement
- Interactive editing with gestures
- Programmatic control with controller
- All callback APIs
- Custom stamp rendering
- Coordinate conversion utilities
- Complete workflow from placement to export
Run the example:
cd example
flutter run
API Reference #
| Class | Description |
|---|---|
PdfStampEditorPage |
Main widget for PDF stamp editing |
PdfStampEditorController |
Controller for programmatic stamp management |
PdfStamp |
Base class for stamps (sealed) |
ImageStamp |
Image stamp with PNG bytes |
TextStamp |
Text stamp with customizable text |
TextStampConfig |
Configuration for text stamp creation |
ImageStampConfig |
Configuration for image stamp creation |
StampEditorMode |
Interaction mode enum (none, text, image) |
SelectionConfig |
Configuration for selection styling |
DeleteButtonConfig |
Configuration for delete button styling |
PdfStampEditorExporter |
Export engine for applying stamps to PDFs |
PdfCoordinateConverter |
Utilities for coordinate conversion |
MatrixCalculator |
Calculate PDF transformation matrices |
For detailed API documentation, see the API reference (available after publishing to pub.dev).
Limitations #
- Web export: Not supported (requires FFI/PDFium). Consider using a backend service or JavaScript/WASM-based PDF writer for web export.
- Desktop platforms: Windows/macOS/Linux are not currently supported
- Concurrent operations: The PDF viewer must be hidden during export to prevent concurrent PDFium calls
Dependencies #
pdfrx: PDF viewing and renderingimage: PNG image processingfile_picker: File selection (optional)path_provider: File system access (optional)path: Path manipulation utilities
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Additional Information #
For more information, issues, or feature requests, please visit the GitHub repository.