Interactive PDF
A Flutter package for generating PDFs with interactive hotspot annotations that reveal embedded text or images on long-press. The annotations are stored persistently in the PDF and work across sharing and reopening.
✨ Features
- 🎯 Interactive Hotspots - Define regions that reveal hidden content on long-press
- 📝 Text & Image Annotations - Embed text, images, or custom data
- 💾 Persistent Storage - Annotations are stored inside the PDF file
- 🔄 Share & Reopen - Works after sharing or reopening the PDF
- 📱 Cross-Platform - Works on iOS, Android, Web, macOS, Windows, and Linux
- 🎨 Customizable Styling - Full control over hotspot appearance and overlays
- ⚡ Performance Optimized - LRU caching, pre-rendering, and efficient memory management
- 📄 Standard PDF Compatibility - PDFs remain valid in any PDF viewer
🌍 Platform Support
| Platform | Supported | Notes |
|---|---|---|
| iOS | ✅ | Full support |
| Android | ✅ | Full support |
| Web | ✅ | File download via browser |
| macOS | ✅ | Full support |
| Windows | ✅ | Full support |
| Linux | ✅ | Full support |
Platform-specific File Saving
import 'package:interactive_pdf/interactive_pdf.dart';
// Save PDF bytes - works on all platforms
final bytes = await doc.save();
// Save to file - platform-aware
if (!isWeb) {
// Native platforms: saves to specified path
await doc.saveToFile('/path/to/file.pdf');
} else {
// Web: triggers browser download
await saveToFile('document.pdf', bytes);
}
// Check platform
if (isWeb) {
// Running on web
} else if (isMobile) {
// Running on iOS or Android
} else if (isDesktop) {
// Running on macOS, Windows, or Linux
}
📦 Installation
Add to your pubspec.yaml:
dependencies:
interactive_pdf: ^0.1.0
🚀 Quick Start
Creating an Interactive PDF
import 'package:interactive_pdf/interactive_pdf.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
// Create document
final doc = AdvancePdfDocument(
title: 'My Interactive PDF',
author: 'Your Name',
);
// Add a page
doc.addPage(
pageFormat: PdfPageFormat.a4,
build: (context) => pw.Center(
child: pw.Text('Hello World!'),
),
);
// Add a text hotspot
doc.addTextHotspot(
pageIndex: 0,
rect: const PdfHotspotRect(
left: 100,
bottom: 700,
width: 200,
height: 50,
),
text: 'This is hidden text that appears on long-press!',
title: 'Secret Info',
);
// Save PDF
final bytes = await doc.save();
Previewing with Interactive Hotspots
import 'package:flutter/material.dart';
import 'package:interactive_pdf/interactive_pdf.dart';
class PdfPreviewScreen extends StatelessWidget {
final Uint8List pdfBytes;
const PdfPreviewScreen({required this.pdfBytes});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('PDF Preview')),
body: PdfInteractivePreview(
pdfBytes: pdfBytes,
style: PdfHotspotStyle.withIndicators,
onHotspotActivated: (annotation) {
print('Hotspot activated: ${annotation.id}');
},
),
);
}
}
📚 API Reference
AdvancePdfDocument
The main class for creating PDFs with hotspot support.
final doc = AdvancePdfDocument(
title: 'Document Title',
author: 'Author Name',
subject: 'Subject',
keywords: ['flutter', 'pdf'],
);
// Add pages
int pageIndex = doc.addPage(
pageFormat: PdfPageFormat.a4,
build: (context) => pw.Text('Content'),
);
// Add hotspots
doc.addTextHotspot(
pageIndex: 0,
rect: PdfHotspotRect(...),
text: 'Hidden text',
);
doc.addImageHotspot(
pageIndex: 0,
rect: PdfHotspotRect(...),
imageBytes: myImageBytes,
);
// Generic hotspot
doc.addHotspot(
pageIndex: 0,
rect: PdfHotspotRect(...),
type: PdfAnnotationType.custom,
content: PdfAnnotationContent(
text: 'Text',
customPayload: {'key': 'value'},
),
);
// Save
final bytes = await doc.save();
await doc.saveToFile('/path/to/file.pdf');
PdfHotspotRect
Defines a rectangular region in PDF coordinates (points, 1/72 inch).
// Basic constructor
const rect = PdfHotspotRect(
left: 100, // X from left edge
bottom: 200, // Y from bottom (PDF uses bottom-left origin)
width: 150,
height: 50,
);
// From corners
final rect = PdfHotspotRect.fromLTRB(left, bottom, right, top);
// From Flutter Rect (with coordinate conversion)
final rect = PdfHotspotRect.fromFlutterRect(flutterRect, pageHeight);
PdfAnnotationContent
Content payload for hotspot annotations.
// Text content
final content = PdfAnnotationContent.text(
'Hidden message',
title: 'Title',
);
// Image content
final content = PdfAnnotationContent.image(
imageBytes,
title: 'Hidden Image',
);
// Full constructor
final content = PdfAnnotationContent(
text: 'Description',
imageBytes: imageData,
title: 'Title',
description: 'Subtitle',
customPayload: {'extra': 'data'},
);
PdfInteractivePreview
Widget for displaying PDFs with interactive hotspot support.
PdfInteractivePreview(
pdfBytes: pdfBytes,
// Optional: Pre-parsed hotspots (auto-parsed if null)
hotspots: myHotspots,
// Callbacks
onHotspotActivated: (annotation) { },
onHotspotDismissed: (annotation) { },
onPageChanged: (page) { },
// Customization
style: PdfHotspotStyle.light,
overlayBuilder: (context, annotation) => MyCustomOverlay(),
// Behavior
enableZoom: true,
enablePageNavigation: true,
showPageNumbers: true,
// Display
backgroundColor: Colors.grey,
pagePadding: EdgeInsets.all(8),
minScale: 1.0,
maxScale: 4.0,
renderDpi: 150,
// External controller
controller: myController,
)
PdfInteractiveController
Programmatic control over the preview widget.
final controller = PdfInteractiveController();
// Initialize with PDF
await controller.initialize(pdfBytes: bytes);
// Navigation
controller.jumpToPage(2);
controller.nextPage();
controller.previousPage();
// Hotspot control
controller.showHotspot('hotspot-id');
controller.hideHotspot();
// Zoom
controller.setScale(2.0);
controller.resetZoom();
// Properties
int currentPage = controller.currentPage;
int pageCount = controller.pageCount;
List<PdfHotspotAnnotation> hotspots = controller.hotspots;
// Clean up
controller.dispose();
PdfHotspotStyle
Customize the visual appearance of hotspots and overlays.
// Preset styles
PdfHotspotStyle.light // Default light theme
PdfHotspotStyle.dark // Dark theme
PdfHotspotStyle.minimal // No visual indicators
PdfHotspotStyle.withIndicators // Visible hotspot regions
// Custom style
final style = PdfHotspotStyle(
highlightColor: Colors.blue.withOpacity(0.3),
borderRadius: 8.0,
borderWidth: 2.0,
borderColor: Colors.blue,
popupBackgroundColor: Colors.white,
popupTextColor: Colors.black87,
popupMaxWidth: 300,
popupMaxHeight: 400,
animationDuration: Duration(milliseconds: 200),
longPressDuration: Duration(milliseconds: 500),
showHotspotIndicator: true,
indicatorColor: Colors.blue.withOpacity(0.2),
);
// Modify existing style
final modified = PdfHotspotStyle.light.copyWith(
borderRadius: 12.0,
showHotspotIndicator: true,
);
AdvancePdfParser
Parse existing PDFs to extract hotspot annotations.
// Parse hotspots from PDF bytes
final hotspots = await AdvancePdfParser.parseHotspots(pdfBytes);
// Get full manifest
final manifest = await AdvancePdfParser.parseManifest(pdfBytes);
// Check if PDF has hotspots
final hasHotspots = await AdvancePdfParser.hasHotspots(pdfBytes);
// Get page dimensions
final dimensions = await AdvancePdfParser.getPageDimensions(pdfBytes);
🎨 Custom Overlay Builder
Create custom UI for displaying annotation content:
PdfInteractivePreview(
pdfBytes: pdfBytes,
overlayBuilder: (context, annotation) {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (annotation.content.title != null)
Text(
annotation.content.title!,
style: TextStyle(fontWeight: FontWeight.bold),
),
if (annotation.content.imageBytes != null)
Image.memory(annotation.content.imageBytes!),
if (annotation.content.text != null)
Text(annotation.content.text!),
],
),
),
);
},
)
⚡ Performance Tips
- Use appropriate DPI: Lower
renderDpi(100-150) for faster rendering, higher (200-300) for print quality - Pre-render pages: The preview automatically pre-renders adjacent pages
- Reuse controllers: Create controller once and reuse across rebuilds
- Optimize images: Compress image hotspots before embedding
- Use shared assets: For repeated images, use
addSharedAsset()and reference by key
🔧 How It Works
- Storage: Hotspot data is stored as PDF annotations with custom
/APDF_Hotspotdictionaries plus an embedded JSON manifest - Compatibility: The PDF remains valid in any viewer; custom data is simply ignored by standard viewers
- Parsing: When reopening, the manifest is extracted and hotspots are reconstructed
- Rendering: Pages are rasterized using the
printingpackage with LRU caching - Interaction: Long-press gesture detection with coordinate mapping between PDF and Flutter spaces
📄 License
MIT License - see LICENSE for details.
🤝 Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
Libraries
- interactive_pdf
- Interactive PDF with Hotspot Annotations