t_pdf_reader 1.1.0
t_pdf_reader: ^1.1.0 copied to clipboard
A performant Flutter PDF viewer capable of handling large PDF files smoothly. Built on than_pdf_engine, this package provides a clean foundation for custom PDF implementations, allowing users to build [...]
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 #
- Basic Usage
- Built In Scrollbar Styles
- Controller
- Custom Footer
- Dark Mode
- Reader Actions
- Reader State
- Reader Streams
- Complete Example
- License
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.