pdf_to_image_converter 0.0.5 copy "pdf_to_image_converter: ^0.0.5" to clipboard
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.

example/lib/main.dart

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),
                  ),
                ],
              ),
            ),
        ],
      ),
    );
  }
}
16
likes
160
points
233
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter package for converting PDF to images with quality presets, rotation, thumbnails, batch processing, and metadata extraction.

Repository (GitHub)
View/report issues

Topics

#pdf #image #converter #pdf-to-image #rendering

Documentation

Documentation
API reference

License

BSD-3-Clause (license)

Dependencies

file_picker, flutter, pdf_image_renderer

More

Packages that depend on pdf_to_image_converter