native_pdf_view

Flutter Plugin to render PDF and show a PDF file on Web, MacOs 10.11+, Android 5.0+ and iOS.

Showcase

LiveScreenshot

Getting Started

In your flutter project add the dependency:

pub package

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 PdfView1
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

Libraries

native_pdf_view