Friday, December 1, 2023

Flutter Showing PDF file easy

Flutter plugin to render PDF and show a PDF file on WebMacOs 10.11+Android 5.0+ and iOS.

demo

Installation

In your flutter project add the dependency:

dependencies:
  native_pdf_view: any

For web add lines in index.html before importing main.dart.js:

<script src="//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.min.js"></script>
<script type="text/javascript">
  pdfjsLib.GlobalWorkerOptions.workerSrc = "//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.worker.min.js";
</script>

Usage example

It very simple!

import 'package:native_pdf_view/native_pdf_view.dart';

final pdfController = PdfController(
  document: PdfDocument.openAsset('assets/sample.pdf'),
);

Widget pdfView() => PdfView(
  controller: pdfController,
);

Api

PdfController

ParameterDescriptionDefault
documentThe document to be displayed
initialPageThe page to show when first creating the [PdfView]1
viewportFractionThe fraction of the viewport that each page should occupy.1.0

PdfView

ParameterDescriptionDefault
controllerPages control. See page control and additional pdf info
onPageChangedCalled whenever the page in the center of the viewport changes. See Document callbacks
onDocumentLoadedCalled when a document is loaded. See Document callbacks
onDocumentErrorCalled when a document loading error. Exception is passed in the attributes
documentLoaderWidget showing when pdf document loadingSizedBox()
pageLoaderWidget showing when pdf page loadingSizedBox()
builderCallback called to render a widget for each page. See custom page builderDefault builder
errorBuilderShow document loading error message inside PdfViewCentered error text
rendererCustom PdfRenderer library options. See custom renderer optionswidth: page.width * 2
height: page.height * 2
format: PdfPageFormat.JPEG
backgroundColor: ‘#ffffff’
scrollDirectionPage turning directionAxis.horizontal
physicsHow the widgets should respond to user input
pageSnappingSet to false for mouse wheel scroll on webtrue

Additional examples

Open another document

pdfController.openDocument(PdfDocument.openAsset('assets/sample.pdf'));

Page control:

// Jump to specified page
pdfController.jumpTo(3);

// Animate to specified page
_pdfController.animateToPage(3, duration: Duration(milliseconds: 250), curve: Curves.ease);

// Animate to next page 
_pdfController.nextPage(duration: Duration(milliseconds: 250), curve: Curves.easeIn);

// Animate to previous page
_pdfController.previousPage(duration: Duration(milliseconds: 250), curve: Curves.easeOut);

Additional pdf info:

// Actual showed page
pdfController.page;

// Count of all pages in document
pdfController.pagesCount;

Document callbacks

int _actualPageNumber = 0, _allPagesCount = 0;

PdfView(
  controller: pdfController,
  onDocumentLoaded: (document) {
    setState(() {
      _allPagesCount = document.pagesCount;
    });
  },
  onPageChanged: (page) {
    setState(() {
      _actualPageNumber = page;
    });
  },
);

/// Now you can use these values to display the reading status of the document.
Text('Read: $_actualPageNumber of $_allPagesCount');

Custom renderer options

PdfView(
  controller: pdfController,
  renderer: (PdfPage page) => page.render(
    width: page.width * 2,
    height: page.height * 2,
    format: PdfPageFormat.JPEG,
    backgroundColor: '#FFFFFF',
  ),
);

Custom page builder:

PdfView(
  controller: pdfController,
  document: snapshot.data,
  pageBuilder: (
    PdfPageImage pageImage, 
    bool isCurrentIndex, 
    AnimationController animationController,
  ) {
    // Double tap scales
    final List<double> _doubleTapScales = <double>[1.0, 2.0, 3.0]
    // Double tap animation
    Animation<double> _doubleTapAnimation;
    void Function() _animationListener;

    Widget image = ExtendedImage.memory(
      pageImage.bytes,
      key: Key(pageImage.hashCode.toString()),
      fit: BoxFit.contain,
      mode: ExtendedImageMode.gesture,
      initGestureConfigHandler: (_) => GestureConfig(
        minScale: 1,
        maxScale: 3.0,
        animationMinScale: .75,
        animationMaxScale: 3.0,
        speed: 1,
        inertialSpeed: 100,
        inPageView: true,
        initialScale: 1.0,
        cacheGesture: false,
      ),
      onDoubleTap: (ExtendedImageGestureState state) {
        final pointerDownPosition = state.pointerDownPosition;
        final begin = state.gestureDetails.totalScale;
        double end;

        _doubleTapAnimation?.removeListener(_animationListener);

        animationController
          ..stop()
          ..reset();

        if (begin == _doubleTapScales[0]) {
          end = _doubleTapScales[1];
        } else {
          if (begin == _doubleTapScales[1]) {
            end = _doubleTapScales[2];
          } else {
            end = _doubleTapScales[0];
          }
        }

        _animationListener = () {
          //print(_animation.value);
          state.handleDoubleTap(
              scale: _doubleTapAnimation.value,
              doubleTapPosition: pointerDownPosition);
        };
        _doubleTapAnimation = animationController
            .drive(Tween<double>(begin: begin, end: end))
              ..addListener(_animationListener);

        animationController.forward();
      },
    );
    if (isCurrentIndex) {
      image = Hero(
        tag: 'pdf_view' + pageImage.pageNumber.toString(),
        child: image,
      );
    }
    return image;
  },
);

Rendering additional info

On Web

This plugin uses the PDF.js

On Android

This plugin uses the Android native PdfRenderer

On Ios & MacOs

This plugin uses the IOS native CGPDFPage