pdf_to_image_converter 0.0.5
pdf_to_image_converter: ^0.0.5 copied to clipboard
Flutter package for converting PDF to images with quality presets, rotation, thumbnails, batch processing, and metadata extraction.
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:pdf_to_image_converter/pdf_to_image_converter.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PDF to Image Converter - Full Features Demo',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: const PdfConverterDemo(),
);
}
}
class PdfConverterDemo extends StatefulWidget {
const PdfConverterDemo({super.key});
@override
State<PdfConverterDemo> createState() => _PdfConverterDemoState();
}
class _PdfConverterDemoState extends State<PdfConverterDemo> {
final PdfImageConverter _converter = PdfImageConverter();
Uint8List? _currentImage;
List<Uint8List?> _thumbnails = [];
RenderQuality _selectedQuality = RenderQuality.high;
PageRotation _selectedRotation = PageRotation.rotate0;
bool _isLoading = false;
String _statusMessage = '';
CancellationToken? _cancellationToken;
int _startPage = 0;
int _endPage = 0;
@override
void dispose() {
_converter.closePdf();
super.dispose();
}
void _showMessage(String message) {
setState(() => _statusMessage = message);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), duration: const Duration(seconds: 2)),
);
}
Future<void> _pickAndOpenPdf() async {
try {
setState(() => _isLoading = true);
final path = await PdfPicker.pickPdf();
if (path != null) {
await _converter.openPdf(path);
// Display metadata
final metadata = _converter.metadata;
_showMessage(
'PDF Opened: ${metadata?.title ?? "Unknown"}\n'
'Pages: ${_converter.pageCount}',
);
// Reset page range
setState(() {
_startPage = 0;
_endPage = _converter.pageCount - 1;
});
// Generate thumbnails for all pages
await _generateAllThumbnails();
// Render first page
await _renderCurrentPage();
}
} catch (e) {
_showMessage('Error opening PDF: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _generateAllThumbnails() async {
if (!_converter.isOpen) return;
setState(() {
_isLoading = true;
_statusMessage = 'Generating thumbnails...';
});
try {
_cancellationToken = CancellationToken();
final thumbnails = await _converter.renderAllThumbnails(
maxWidth: 120,
maxHeight: 160,
cancellationToken: _cancellationToken,
onProgress: (current, total) {
setState(() {
_statusMessage = 'Thumbnail: $current/$total';
});
},
);
setState(() {
_thumbnails = thumbnails;
_statusMessage = 'Generated ${thumbnails.length} thumbnails';
});
} catch (e) {
_showMessage('Error generating thumbnails: $e');
} finally {
setState(() => _isLoading = false);
_cancellationToken = null;
}
}
Future<void> _renderCurrentPage() async {
if (!_converter.isOpen) return;
setState(() => _isLoading = true);
try {
final image = await _converter.renderPage(
_converter.currentPage,
quality: _selectedQuality,
rotation: _selectedRotation,
);
setState(() {
_currentImage = image;
_statusMessage = 'Page ${_converter.currentPage + 1} rendered';
});
} catch (e) {
_showMessage('Error rendering page: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _renderPageRange() async {
if (!_converter.isOpen) return;
setState(() {
_isLoading = true;
_statusMessage = 'Rendering page range...';
});
try {
_cancellationToken = CancellationToken();
final images = await _converter.renderPageRange(
startPage: _startPage,
endPage: _endPage,
quality: _selectedQuality,
rotation: _selectedRotation,
cancellationToken: _cancellationToken,
onProgress: (current, total) {
setState(() {
_statusMessage = 'Rendering: $current/$total';
});
},
);
// Save the rendered range
final directory = await getApplicationDocumentsDirectory();
final outputDir = '${directory.path}/pdf_page_range';
final saved = await _converter.saveAllImages(
images,
outputDir,
fileNamePattern: 'page_{index}.png',
cancellationToken: _cancellationToken,
onProgress: (current, total, success) {
setState(() {
_statusMessage = 'Saving: $current/$total';
});
},
);
_showMessage('Saved $saved images to $outputDir');
} catch (e) {
_showMessage('Error rendering range: $e');
} finally {
setState(() => _isLoading = false);
_cancellationToken = null;
}
}
Future<void> _renderWithCustomSettings() async {
if (!_converter.isOpen || _converter.pageCount < 3) {
_showMessage('Need at least 3 pages for this demo');
return;
}
setState(() {
_isLoading = true;
_statusMessage = 'Rendering with custom settings...';
});
try {
_cancellationToken = CancellationToken();
// Different settings for different pages
final configs = [
PageRenderConfig(pageIndex: 0, scale: 4.0, background: Colors.white),
PageRenderConfig(
pageIndex: 1,
scale: 2.0,
rotation: PageRotation.rotate90,
),
if (_converter.pageCount > 2)
PageRenderConfig(
pageIndex: 2,
scale: 3.0,
rotation: PageRotation.rotate180,
),
];
final images = await _converter.renderPagesWithConfig(
configs,
cancellationToken: _cancellationToken,
onProgress: (current, total) {
setState(() {
_statusMessage = 'Custom rendering: $current/$total';
});
},
);
final directory = await getApplicationDocumentsDirectory();
final outputDir = '${directory.path}/pdf_custom_settings';
final saved = await _converter.saveAllImages(
images,
outputDir,
fileNamePattern: 'custom_page_{index}.png',
);
_showMessage('Saved $saved images with custom settings to $outputDir');
} catch (e) {
_showMessage('Error: $e');
} finally {
setState(() => _isLoading = false);
_cancellationToken = null;
}
}
Future<void> _renderAllPages() async {
if (!_converter.isOpen) return;
setState(() {
_isLoading = true;
_statusMessage = 'Rendering all pages...';
});
try {
_cancellationToken = CancellationToken();
final images = await _converter.renderAllPages(
quality: _selectedQuality,
rotation: _selectedRotation,
cancellationToken: _cancellationToken,
onProgress: (current, total) {
setState(() {
_statusMessage = 'Rendering: $current/$total';
});
},
);
final directory = await getApplicationDocumentsDirectory();
final outputDir = '${directory.path}/pdf_all_pages';
final saved = await _converter.saveAllImages(
images,
outputDir,
fileNamePattern: 'page_{index}.png',
cancellationToken: _cancellationToken,
onProgress: (current, total, success) {
setState(() {
_statusMessage = 'Saving: $current/$total';
});
},
);
_showMessage('Saved $saved images to $outputDir');
} catch (e) {
_showMessage('Error: $e');
} finally {
setState(() => _isLoading = false);
_cancellationToken = null;
}
}
Future<void> _showPageSizes() async {
if (!_converter.isOpen) return;
setState(() => _isLoading = true);
try {
final sizes = await _converter.getAllPageSizes();
showDialog(
// ignore: use_build_context_synchronously
context: context,
builder: (context) => AlertDialog(
title: const Text('Page Sizes'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: sizes.length,
itemBuilder: (context, index) {
final size = sizes[index];
return ListTile(
title: Text('Page ${index + 1}'),
subtitle: Text(
'${size.width.toStringAsFixed(1)} x ${size.height.toStringAsFixed(1)} pts',
),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
} catch (e) {
_showMessage('Error: $e');
} finally {
setState(() => _isLoading = false);
}
}
void _cancelOperation() {
_cancellationToken?.cancel();
_showMessage('Operation cancelled');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('PDF to Image - All Features'),
actions: [
if (_isLoading && _cancellationToken != null)
IconButton(
icon: const Icon(Icons.cancel),
onPressed: _cancelOperation,
tooltip: 'Cancel Operation',
),
],
),
body: Column(
children: [
// Control Panel
Card(
margin: const EdgeInsets.all(8),
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// PDF Info
if (_converter.isOpen) ...[
Text(
'PDF: ${_converter.metadata?.title ?? "Unknown"}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text('Pages: ${_converter.pageCount}'),
const Divider(),
],
// Quality Selector
Row(
children: [
const Text('Quality: '),
const SizedBox(width: 8),
Expanded(
child: DropdownButton<RenderQuality>(
value: _selectedQuality,
isExpanded: true,
items: RenderQuality.values.map((quality) {
return DropdownMenuItem(
value: quality,
child: Text(
'${quality.name} (${quality.scale}x)',
),
);
}).toList(),
onChanged: (value) {
if (value != null) {
setState(() => _selectedQuality = value);
}
},
),
),
],
),
// Rotation Selector
Row(
children: [
const Text('Rotation: '),
const SizedBox(width: 8),
Expanded(
child: DropdownButton<PageRotation>(
value: _selectedRotation,
isExpanded: true,
items: PageRotation.values.map((rotation) {
return DropdownMenuItem(
value: rotation,
child: Text('${rotation.degrees}°'),
);
}).toList(),
onChanged: (value) {
if (value != null) {
setState(() => _selectedRotation = value);
if (_converter.isOpen) _renderCurrentPage();
}
},
),
),
],
),
// Page Range Selector
if (_converter.isOpen) ...[
const Divider(),
const Text('Page Range:'),
Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'Start',
),
keyboardType: TextInputType.number,
controller: TextEditingController(
text: '${_startPage + 1}',
),
onChanged: (value) {
final page = int.tryParse(value);
if (page != null &&
page > 0 &&
page <= _converter.pageCount) {
_startPage = page - 1;
}
},
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
decoration: const InputDecoration(labelText: 'End'),
keyboardType: TextInputType.number,
controller: TextEditingController(
text: '${_endPage + 1}',
),
onChanged: (value) {
final page = int.tryParse(value);
if (page != null &&
page > 0 &&
page <= _converter.pageCount) {
_endPage = page - 1;
}
},
),
),
],
),
],
const SizedBox(height: 8),
// Action Buttons
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
onPressed: _isLoading ? null : _pickAndOpenPdf,
icon: const Icon(Icons.folder_open),
label: const Text('Open PDF'),
),
if (_converter.isOpen) ...[
ElevatedButton.icon(
onPressed: _isLoading ? null : _showPageSizes,
icon: const Icon(Icons.straighten),
label: const Text('Page Sizes'),
),
ElevatedButton.icon(
onPressed: _isLoading ? null : _renderPageRange,
icon: const Icon(Icons.pages),
label: const Text('Render Range'),
),
ElevatedButton.icon(
onPressed: _isLoading
? null
: _renderWithCustomSettings,
icon: const Icon(Icons.settings),
label: const Text('Custom Settings'),
),
ElevatedButton.icon(
onPressed: _isLoading ? null : _renderAllPages,
icon: const Icon(Icons.select_all),
label: const Text('Render All'),
),
],
],
),
// Status
if (_statusMessage.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
_statusMessage,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 12,
),
),
],
if (_isLoading) const LinearProgressIndicator(),
],
),
),
),
// Thumbnail Strip
if (_thumbnails.isNotEmpty) ...[
const Padding(
padding: EdgeInsets.all(8),
child: Text(
'Thumbnails (tap to view):',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _thumbnails.length,
itemBuilder: (context, index) {
final thumbnail = _thumbnails[index];
if (thumbnail == null) return const SizedBox();
return GestureDetector(
onTap: () async {
final image = await _converter.renderPage(
index,
quality: _selectedQuality,
rotation: _selectedRotation,
);
setState(() => _currentImage = image);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
border: Border.all(
color: _converter.currentPage == index
? Colors.blue
: Colors.grey,
width: 2,
),
),
child: Column(
children: [
Expanded(
child: Image.memory(thumbnail, fit: BoxFit.contain),
),
Text(
'${index + 1}',
style: const TextStyle(fontSize: 10),
),
],
),
),
);
},
),
),
],
// Main Image Display
Expanded(
child: _currentImage != null
? InteractiveViewer(
child: Center(child: Image.memory(_currentImage!)),
)
: const Center(child: Text('Open a PDF to begin')),
),
// Navigation
if (_converter.isOpen)
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: _converter.currentPage > 0 && !_isLoading
? () async {
final image = await _converter.renderPage(
_converter.currentPage - 1,
quality: _selectedQuality,
rotation: _selectedRotation,
);
setState(() => _currentImage = image);
}
: null,
icon: const Icon(Icons.chevron_left),
),
Text(
'Page ${_converter.currentPage + 1} / ${_converter.pageCount}',
),
IconButton(
onPressed:
_converter.currentPage < _converter.pageCount - 1 &&
!_isLoading
? () async {
final image = await _converter.renderPage(
_converter.currentPage + 1,
quality: _selectedQuality,
rotation: _selectedRotation,
);
setState(() => _currentImage = image);
}
: null,
icon: const Icon(Icons.chevron_right),
),
],
),
),
],
),
);
}
}