t_pdf_reader 1.3.0 copy "t_pdf_reader: ^1.3.0" to clipboard
t_pdf_reader: ^1.3.0 copied to clipboard

A performant Flutter PDF viewer for smoothly reading large PDF files, powered by than_pdf_engine.

T PDF Reader #

A customizable PDF reader widget for Flutter, designed for smooth document viewing with programmatic control, zooming, page navigation, custom UI components, scrollbar customization, and image caching.

Features #

  • 📄 PDF document rendering
  • 🔍 Zoom in / zoom out
  • 🎯 Fit-to-view zoom
  • 📖 Jump to a specific page
  • 🖱️ Mouse wheel and pointer scrolling
  • 👆 Touch scrolling and pinch-to-zoom
  • 🖥️ Desktop-friendly interaction
  • 📱 Mobile gesture support
  • 🎨 Custom footer widgets
  • 📜 Fully customizable scrollbar
  • 🌙 Custom dark-mode support
  • 🖼️ Image cache state listeners
  • 🔄 Reactive controller streams
  • ⚡ Programmatic reader actions
  • 🧩 Extensible reader UI

Short #


Screenshots #

Desktop #

Mobile #

Basic Usage #

import 'package:flutter/material.dart';
import 'package:t_pdf_reader/t_pdf_reader.dart';

class MyReader extends StatefulWidget {
  const MyReader({
    super.key,
    required this.path,
  });

  final String path;

  @override
  State<MyReader> createState() => _MyReaderState();
}

class _MyReaderState extends State<MyReader> {
  final controller = TPdfController();

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TPdfReader(
        path: widget.path,
        controller: controller,
      ),
    );
  }
}

Controller #

TPdfController provides access to reader state, actions, and streams.

final controller = TPdfController();

The controller can be used to:

  • Control zoom
  • Navigate between pages
  • Listen for reader events
  • Access the current reader state
  • Customize reader widgets
  • Control scrollbar visibility
  • Monitor image cache state

Reader Actions #

Fit Zoom #

Automatically fit the PDF to the available viewport.

controller.action.setFitZoom();

Zoom In #

controller.action.zoomIn();

Zoom Out #

controller.action.zoomOut();

Set Zoom #

controller.action.setZoom(1.5);

Jump to Page #

controller.action.jumpPage(100);

Reader State #

The current reader state is available through the controller.

final zoom = controller.state.zoom;
final page = controller.state.currentPage;

For example:

controller.stream.zoomChanged.listen((_) {
  print('Zoom: ${controller.state.zoom}');
});

Reader Streams #

The controller exposes reactive streams for reader state changes.

Ready #

controller.stream.ready.listen((_) {
  print('PDF reader is ready');
});

Zoom Changed #

controller.stream.zoomChanged.listen((_) {
  print('Zoom: ${controller.state.zoom}');
});

Attached #

The attached stream can be used to detect when the reader controller is attached to a TPdfReader.

controller.attached.listen((_) {
  print('Reader attached');
});

Custom Reader Widgets #

TPdfController supports custom reader UI through TPdfWidgetBuilder.

final controller = TPdfController(
  widgetBuilder: TPdfWidgetBuilder(
    footerBuilder: (context, page) {
      return Text(
        'Page: $page',
      );
    },
  ),
);

This allows applications to customize parts of the reader without modifying the reader itself.


Custom Footer #

You can provide your own footer widget using footerBuilder.

final controller = TPdfController(
  widgetBuilder: TPdfWidgetBuilder(
    footerBuilder: (context, page) {
      return Container(
        padding: const EdgeInsets.all(8),
        child: Text(
          'Page: $page',
        ),
      );
    },
  ),
);

The page parameter represents the current page index.


Custom Scrollbar #

The scrollbar can be completely customized using scrollbarBuilder.

final controller = TPdfController(
  widgetBuilder: TPdfWidgetBuilder(
    scrollbarBuilder: (context, page) {
      return .new(
        widgetInfo: .new(
          thumbWidth: 20,
          thumbHeight: 40,
        ),
        builder: defaultScrollbarGlow(
          thumbWidth: 20,
          thumbHeight: 40,
        ),
      );
    },
  ),
);

The scrollbar builder receives the current page, allowing the scrollbar to also be used as a page indicator.


Custom Scrollbar UI #

You can create any Flutter widget for the scrollbar.

scrollbarBuilder: (context, page) {
  final colorScheme = Theme.of(context).colorScheme;

  return .new(
    widgetInfo: .new(
      thumbWidth: 32,
      thumbHeight: 28,
      positionRight: 12,
    ),
    builder: DecoratedBox(
      decoration: BoxDecoration(
        color: colorScheme.primaryContainer,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(
          color: colorScheme.outlineVariant,
        ),
      ),
      child: Center(
        child: Text(
          '${page + 1}',
          style: TextStyle(
            color: colorScheme.onPrimaryContainer,
            fontSize: 11,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    ),
  );
},

This makes it possible to build:

  • Minimal scrollbars
  • Rounded scrollbars
  • Neon scrollbars
  • Glass-style scrollbars
  • Page indicator scrollbars
  • Custom branded scrollbars

Built-in Scrollbar Styles #

Built-in scrollbar styles can be used directly.

builder: defaultScrollbarGlow(
  thumbWidth: 20,
  thumbHeight: 40,
),

Example:

scrollbarBuilder: (context, page) {
  return .new(
    widgetInfo: .new(
      thumbWidth: 20,
      thumbHeight: 40,
    ),
    builder: defaultScrollbarGlow(
      thumbWidth: 20,
      thumbHeight: 40,
    ),
  );
},

Scrollbar Toggle #

The scrollbar visibility can be controlled from the UI.

PdfScrollbarToggler(
  controller: controller,
)

This is useful for applications that want to provide a reader toolbar with a scrollbar visibility toggle.


Page Navigation #

A page listener can be used to display the current page and trigger page navigation.

PdfPageListener(
  controller: controller,
  onClicked: jumpPage,
)

For example:

void jumpPage() {
  showDialog(
    context: context,
    builder: (context) {
      return PdfPageJumpDialog(
        controller: controller,
      );
    },
  );
}

Zoom Controls #

Built-in zoom controls can be added to a reader toolbar.

PdfZoomOut(
  controller: controller,
),

PdfZoomIn(
  controller: controller,
),

PdfZoomListener(
  controller: controller,
),

This provides:

  • Zoom out button
  • Zoom in button
  • Current zoom display/listening

Image Cache #

The reader exposes image cache events through the controller.

PdfCacheImageListener(
  controller: controller,
)

This can be used to monitor PDF page image caching and provide cache-related UI.


Dark Mode #

The reader can be combined with Flutter's ColorFiltered widget to create a custom dark reading mode.

bool darkMode = false;
ColorFiltered(
  colorFilter: ColorFilter.mode(
    Colors.white,
    darkMode
        ? BlendMode.difference
        : BlendMode.dstIn,
  ),
  child: TPdfReader(
    path: widget.path,
    controller: controller,
  ),
)

Toggle the mode from the UI:

IconButton(
  onPressed: () {
    setState(() {
      darkMode = !darkMode;
    });
  },
  icon: Icon(
    darkMode
        ? Icons.dark_mode_outlined
        : Icons.light_mode_outlined,
  ),
)

Complete Example #

The following example demonstrates a custom reader toolbar, page navigation, zoom controls, dark mode, scrollbar customization, and cache monitoring.

class MyReader extends StatefulWidget {
  const MyReader({super.key, required this.path});
  final String path;

  @override
  State<MyReader> createState() => _MyReaderState();
}

class _MyReaderState extends State<MyReader> {
  late final TPdfController controller;

  @override
  void initState() {
    controller = TPdfController(
      widgetBuilder: TPdfWidgetBuilder(
        footerBuilder: (context, pageOffset) => Container(
          width: pageOffset.width,
          color: Colors.white,
          child: Center(
            child: Text(
              'Page: ${pageOffset.pageIndex + 1}',
              style: TextStyle(color: Colors.black),
            ),
          ),
        ),
        scrollbarBuilder: (context, page) {
          return .new(
            widgetInfo: .new(thumbWidth: 20, thumbHeight: 40),
            builder: defaultScrollbarGlow(thumbWidth: 20, thumbHeight: 40),
          );
        },
      ),
      eventBuilder: .new(
        onKeyEventAfterConfig: (node, event) {
          if (event is KeyDownEvent) {
            if (event.physicalKey == .arrowDown) {
              print('custom down');
              return .handled;
            }
          }
          return .ignored;
        },
      ),
    );
    controller.attached.listen((_) {
      controller.stream.ready.listen((event) {
        controller.action.setFitZoom();
        print('reader ready');
        // controller.action.jumpPage(100);
        // controller.action.setZoom(1.7000000000000006);
        // controller.action.
      });
      controller.stream.zoomChanged.listen((_) {
        print(
          'zoom: ${controller.state.zoom} - currentOffsetX: ${controller.state.currentOffsetX}',
        );
      });
    });
    super.initState();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  bool darkMode = false;

  @override
  Widget build(BuildContext context) {
    return Theme(
      data: .dark(),
      child: Scaffold(
        appBar: AppBar(title: Text('Pdf Reader')),
        body: StreamBuilder(
          stream: controller.attached,
          builder: (context, asyncSnapshot) {
            return Stack(
              children: [
                Positioned.fill(
                  top: 50,
                  child: ColorFiltered(
                    colorFilter: .mode(
                      Colors.white,
                      darkMode ? .difference : .dstIn,
                    ),
                    child: TPdfReader(
                      path: widget.path,
                      controller: controller,
                    ),
                  ),
                ),
                Positioned(
                  top: 0,
                  left: 0,
                  right: 0,
                  height: 50,
                  child: testHeaderWidget(),
                ),
              ],
            );
          },
        ),
      ),
    );
  }

  Widget testHeaderWidget() {
    return Builder(
      builder: (context) {
        final col = Theme.of(context).colorScheme;
        return Container(
          color: col.surfaceContainer,
          child: SingleChildScrollView(
            scrollDirection: .horizontal,
            child: Row(
              spacing: 8,
              children: [
                PdfPageListener(controller: controller, onClicked: jumpPage),
                PdfZoomOut(controller: controller),
                PdfZoomIn(controller: controller),
                PdfZoomListener(controller: controller),
                IconButton(
                  onPressed: () {
                    darkMode = !darkMode;
                    setState(() {});
                  },
                  icon: Icon(
                    darkMode
                        ? Icons.dark_mode_outlined
                        : Icons.light_mode_outlined,
                  ),
                ),
                PdfScrollbarToggler(controller: controller),
                PdfCacheImageListener(controller: controller),
              ],
            ),
          ),
        );
      },
    );
  }

  void jumpPage() {
    showDialog(
      context: context,
      builder: (context) => PdfPageJumpDialog(controller: controller),
    );
  }
}


Architecture #

The reader is designed around a controller-driven architecture.

TPdfReader
    │
    └── TPdfController
          │
          ├── state
          │
          ├── action
          │
          ├── stream
          │
          └── widgetBuilder
                │
                ├── footerBuilder
                └── scrollbarBuilder

This keeps the PDF rendering engine and reader state separate from application-specific UI.


Customization #

The reader is intentionally designed to be customizable.

You can build your own:

  • Header
  • Footer
  • Toolbar
  • Scrollbar
  • Page indicator
  • Zoom controls
  • Dark-mode controls
  • Reader overlays
  • Page navigation UI

The default reader UI can be used as-is, or replaced with application-specific widgets.


License #

Add your project license information here.

0
likes
160
points
247
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A performant Flutter PDF viewer for smoothly reading large PDF files, powered by than_pdf_engine.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

collection, flutter, pdfium_dart, than_pdf_engine

More

Packages that depend on t_pdf_reader