README.md

    pdfrx

    Pdfrx plugin with Aurora support. The plugin supports Aurora, Android, iOS, Windows, macOS, Linux, Web.

    Interactive Demo

    A demo site using Flutter Web

    pdfrx

    Main Features

    Example Code

    The following fragment illustrates the easiest way to show a PDF file in assets:

    import 'package:pdfrx/pdfrx.dart';
    
    ...
    
    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Pdfrx example'),
            ),
            body: PdfViewer.asset('assets/hello.pdf'),
          ),
        );
      }
    }
    

    Anyway, please follow the instructions below to install on your environment.

    Getting Started

    This package is a fork of the original pdfrx. It supports all the features of pdfrx-1.0.82 and includes support for Aurora platform.

    Supports only OS Aurora 5+!

    pubspec.yaml

    dependencies:
      pdfrx:
        git:
          url: https://gitlab.com/omprussia/flutter/flutter-community-plugins/pdfrx.git
          ref: aurora-1.0.84
    

    *.spec

    BuildRequires: pkgconfig(runtime-manager-qt5)
    

    *.desktop

    Permissions=Internet;UserDirs
    

    Note for Windows

    Ensure your Windows installation enables Developer Mode.

    The build process internally uses symbolic link and it requires Developer Mode to be enabled. Without this, you may encounter errors like this.

    Open PDF File

    PdfViewer supports following functions to open PDF file on specific medium:

    Deal with Password Protected PDF Files

    To support password protected PDF files, use passwordProvider to supply passwords interactively:

    PdfViewer.asset(
      'assets/test.pdf',
      // Set password provider to show password dialog
      passwordProvider: _passwordDialog,
    
      ...
    ),
    

    And, _passwordDialog function is defined like this:

    Future<String?> _passwordDialog() async {
      final textController = TextEditingController();
      return await showDialog<String?>(
        context: context,
        barrierDismissible: false,
        builder: (context) {
          return AlertDialog(
            title: const Text('Enter password'),
            content: TextField(
              controller: textController,
              autofocus: true,
              keyboardType: TextInputType.visiblePassword,
              obscureText: true,
              onSubmitted: (value) => Navigator.of(context).pop(value),
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.of(context).pop(null),
                child: const Text('Cancel'),
              ),
              TextButton(
                onPressed: () => Navigator.of(context).pop(textController.text),
                child: const Text('OK'),
              ),
            ],
          );
        },
      );
    }
    

    When PdfViewer tries to open a password protected document, it calls the function passed to passwordProvider (except the first attempt; see below) repeatedly to get a new password until the document is successfully opened. And if the function returns null, the viewer will give up the password trials and the function is no longer called.

    firstAttemptByEmptyPassword

    By default, the first password attempt uses empty password. This is because encrypted PDF files frequently use empty password for viewing purpose. It’s normally useful but if you want to use authoring password, it can be disabled by setting firstAttemptByEmptyPassword to false.

    Customizations

    You can customize the behaviour and visual by configuring PdfViewerParams.

    Text Selection

    Text selection feature is still experimental but you can easily enable it like the following fragment:

    PdfViewer.asset(
      'assets/test.pdf',
      enableTextSelection: true,
      ...
    ),
    

    There are still several limitations and issues on text selection feature:

    • Pan-to-scroll does not work on Desktop (#8)
    • Pan-to-scroll does not work on Flutter Web on mobile devices (#180)
    • Selecting text sometimes throws exception (#185)
      • Text selection suddenly gets cleared in certain situation

    PDF Link Handling

    To enable Link in PDF file, you should set PdfViewerParams.linkHandlerParams.

    The following fragment handles user’s tap on link:

    linkHandlerParams: PdfLinkHandlerParams(
      onLinkTap: (link) {
        // handle URL or Dest
        if (link.url != null) {
          // TODO: implement your own isSecureUrl by yourself...
          if (await isSecureUrl(link.url!)) {
            launchUrl(link.url!);
          }
        } else if (link.dest != null) {
          controller.goToDest(link.dest);
        }
      },
    ),
    

    Note for Link Validation

    For URIs, you should check the validity of the URIs before opening the URI; the example code just show dialog to ask whether to open the URL or not.

    For destinations, you can use PdfViewerController.goToDest to go to the destination. Or you can use PdfViewerController.calcMatrixForDest to get the matrix for it.

    Link Appearance

    For link appearance, you can change its color using PdfLinkHandlerParams.linkColor.

    For more further customization, you can use PdfLinkHandlerParams.customPainter:

    customPainter: (canvas, pageRect, page, links) {
      final paint = Paint()
        ..color = Colors.red.withOpacity(0.2)
        ..style = PaintingStyle.fill;
      for (final link in links) {
        // you can customize here to make your own link appearance
        final rect = link.rect.toRectInPageRect(page: page, pageRect: pageRect);
        canvas.drawRect(rect, paint);
      }
    }
    

    Document Outline (a.k.a Bookmarks)

    PDF defines document outline (PdfOutlineNode), which is sometimes called as bookmarks or index. And you can access it by PdfDocument.loadOutline.

    The following fragment obtains it on PdfViewerParams.onViewerReady:

    onViewerReady: (document, controller) async {
      outline.value = await document.loadOutline();
    },
    

    PdfOutlineNode is tree structured data and for more information, see the usage on example code.

    Horizontal Scroll View

    By default, the pages are layed out vertically. You can customize the layout logic by PdfViewerParams.layoutPages:

    layoutPages: (pages, params) {
      final height =
          pages.fold(0.0, (prev, page) => max(prev, page.height)) +
              params.margin * 2;
      final pageLayouts = <Rect>[];
      double x = params.margin;
      for (var page in pages) {
        pageLayouts.add(
          Rect.fromLTWH(
            x,
            (height - page.height) / 2, // center vertically
            page.width,
            page.height,
          ),
        );
        x += page.width + params.margin;
      }
      return PdfPageLayout(
        pageLayouts: pageLayouts,
        documentSize: Size(x, height),
      );
    },
    

    Facing Pages

    The following code will show pages in “facing-sequential-layout” that is often used in PDF viewer apps:

    /// Page reading order; true to L-to-R that is commonly used by books like manga or such
    var isRightToLeftReadingOrder = false;
    /// Use the first page as cover page
    var needCoverPage = true;
    
    ...
    
    layoutPages: (pages, params) {
      final width = pages.fold(
          0.0, (prev, page) => max(prev, page.width));
    
      final pageLayouts = <Rect>[];
      final offset = needCoverPage ? 1 : 0;
      double y = params.margin;
      for (int i = 0; i < pages.length; i++) {
        final page = pages[i];
        final pos = i + offset;
        final isLeft = isRightToLeftReadingOrder
            ? (pos & 1) == 1
            : (pos & 1) == 0;
    
        final otherSide = (pos ^ 1) - offset;
        final h = 0 <= otherSide && otherSide < pages.length
            ? max(page.height, pages[otherSide].height)
            : page.height;
    
        pageLayouts.add(
          Rect.fromLTWH(
            isLeft
                ? width + params.margin - page.width
                : params.margin * 2 + width,
            y + (h - page.height) / 2,
            page.width,
            page.height,
          ),
        );
        if (pos & 1 == 1 || i + 1 == pages.length) {
          y += h + params.margin;
        }
      }
      return PdfPageLayout(
        pageLayouts: pageLayouts,
        documentSize: Size(
          (params.margin + width) * 2 + params.margin,
          y,
        ),
      );
    },
    

    Showing Scroll Thumbs

    By default, the viewer does never show any scroll bars nor scroll thumbs. You can add scroll thumbs by using PdfViewerParams.viewerOverlayBuilder:

    viewerOverlayBuilder: (context, size, handleLinkTap) => [
      // Add vertical scroll thumb on viewer's right side
      PdfViewerScrollThumb(
        controller: controller,
        orientation: ScrollbarOrientation.right,
        thumbSize: const Size(40, 25),
        thumbBuilder:
            (context, thumbSize, pageNumber, controller) =>
                Container(
          color: Colors.black,
          // Show page number on the thumb
          child: Center(
            child: Text(
              pageNumber.toString(),
              style: const TextStyle(color: Colors.white),
            ),
          ),
        ),
      ),
      // Add horizontal scroll thumb on viewer's bottom
      PdfViewerScrollThumb(
        controller: controller,
        orientation: ScrollbarOrientation.bottom,
        thumbSize: const Size(80, 30),
        thumbBuilder:
            (context, thumbSize, pageNumber, controller) =>
                Container(
          color: Colors.red,
        ),
      ),
    ],
    

    Basically, PdfViewerParams.viewerOverlayBuilder can be used to insert any widgets under viewer’s internal Stack.

    But if you want to place many visual objects that does not interact with user, you’d better use PdfViewerParams.pagePaintCallback.

    Double-tap to Zoom

    You can implement double-tap-to-zoom feature using PdfViewerParams.viewerOverlayBuilder with PdfViewerScrollThumb:

    viewerOverlayBuilder: (context, size, handleLinkTap) => [
      GestureDetector(
        behavior: HitTestBehavior.translucent,
        // Your code here:
        onDoubleTap: () {
          controller.zoomUp(loop: true);
        },
        // If you use GestureDetector on viewerOverlayBuilder, it breaks link-tap handling
        // and you should manually handle it using onTapUp callback
        onTapUp: (details) {
          handleLinkTap(details.localPosition);
        },
        // Make the GestureDetector covers all the viewer widget's area
        // but also make the event go through to the viewer.
        child: IgnorePointer(
          child:
              SizedBox(width: size.width, height: size.height),
        ),
      ),
      ...
    ],
    

    If you want to use PdfViewerScrollThumb with double-tap-to-zoom enabled, place the double-tap-to-zoom code before PdfViewerScrollThumb.

    Adding Page Number on Page Bottom

    If you want to add page number on each page, you can do that by PdfViewerParams.pageOverlaysBuilder:

    pageOverlaysBuilder: (context, pageRect, page) {
      return Align(
        alignment: Alignment.bottomCenter,
        child: Text(page.pageNumber.toString(),
        style: const TextStyle(color: Colors.red)));33
    },
    

    Loading Indicator

    PdfViewer.uri may take long time to download PDF file and you want to show some loading indicator. You can do that by PdfViewerParams.loadingBannerBuilder:

    loadingBannerBuilder: (context, bytesDownloaded, totalBytes) {
      return Center(
        child: CircularProgressIndicator(
          // totalBytes may not be available on certain case
          value: totalBytes != null ? bytesDownloaded / totalBytes : null,
          backgroundColor: Colors.grey,
        ),
      );
    }
    

    Dark/Night Mode Support

    PdfViewer does not have any native dark (or night) mode support but it can be easily implemented using ColorFiltered widget:

    ColorFiltered(
      colorFilter: ColorFilter.mode(Colors.white, darkMode ? BlendMode.difference : BlendMode.dst),
      child: PdfViewer.file(filePath, ...),
    ),
    

    The trick is originally introduced by pckimlong.

    Other Features

    Text Search

    TextSearcher is just a helper class that helps you to implement text searching feature on your app.

    The following fragment illustrates the overall structure of the TextSearcher:

    class _MainPageState extends State<MainPage> {
      final controller = PdfViewerController();
      // create a PdfTextSearcher and add a listener to update the GUI on search result changes
      late final textSearcher = PdfTextSearcher(controller)..addListener(_update);
    
      void _update() {
        if (mounted) {
          setState(() {});
        }
      }
    
      @override
      void dispose() {
        // dispose the PdfTextSearcher
        textSearcher.removeListener(_update);
        textSearcher.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Pdfrx example'),
          ),
          body: PdfViewer.asset(
            'assets/hello.pdf',
            controller: controller,
            params: PdfViewerParams(
              // add pageTextMatchPaintCallback that paints search hit highlights
              pagePaintCallbacks: [
                textSearcher.pageTextMatchPaintCallback
              ],
            ),
          )
        );
      }
      ...
    }
    

    On the fragment above, it does:

    Then, you can use TextSearcher.startTextSearch to search text in the PDF document:

    textSearcher.startTextSearch('hello', caseInsensitive: true);
    

    The search starts running in background and the search progress is notified by the listener.

    There are several functions that helps you to navigate user to the search matches:

    You can get the search result (even during the search running) in the list of PdfTextRange by PdfTextSearcher.matches:

    for (final match in textSearcher.matches) {
      print(match.pageNumber);
      ...
    }
    

    You can also cancel the background search:

    textSearcher.resetTextSearch();
    

    PdfDocumentViewBuilder/PdfPageView

    PdfPageView is just another PDF widget that shows only one page. It accepts PdfDocument and page number to show a page within the document.

    PdfDocumentViewBuilder is used to safely manage PdfDocument inside widget tree and it accepts builder parameter that creates child widgets.

    The following fragment is a typical use of these widgets:

    PdfDocumentViewBuilder.asset(
      'asset/test.pdf',
      builder: (context, document) => ListView.builder(
        itemCount: document?.pages.length ?? 0,
        itemBuilder: (context, index) {
          return Container(
            margin: const EdgeInsets.all(8),
            height: 240,
            child: Column(
              children: [
                SizedBox(
                  height: 220,
                  child: PdfPageView(
                    document: document,
                    pageNumber: index + 1,
                    alignment: Alignment.center,
                  ),
                ),
                Text(
                  '${index + 1}',
                ),
              ],
            ),
          );
        },
      ),
    ),
    

    PdfDocument Management

    PdfDocumentViewBuilder can accept PdfDocumentRef from PdfViewer to safely share the same PdfDocument instance. For more information, see example/viewer/lib/thumbnails_view.dart.

    Конвейеры
    0 успешных
    0 с ошибкой