David PHAM-VAN

Improve PdfPreview

... ... @@ -14,6 +14,7 @@
- Update android projects (mavenCentral, compileSdkVersion 30, gradle:4.1.0)
- Use syscall(SYS_memfd_create) instead of glibc function memfd_create [Obezyan]
- Fix directPrint issue with iOS 15
- Improve PdfPreview actions
## 5.6.6
... ...
... ... @@ -22,8 +22,8 @@ export 'src/asset_utils.dart';
export 'src/cache.dart';
export 'src/callback.dart';
export 'src/fonts/gfonts.dart';
export 'src/preview/actions.dart';
export 'src/preview/pdf_preview.dart';
export 'src/preview/pdf_preview_action.dart';
export 'src/printer.dart';
export 'src/printing.dart';
export 'src/printing_info.dart';
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import '../../printing.dart';
import 'controller.dart';
/// Base Action callback
typedef OnPdfPreviewActionPressed = void Function(
BuildContext context,
LayoutCallback build,
PdfPageFormat pageFormat,
);
mixin PdfPreviewActionBounds {
final childKey = GlobalKey();
/// Calculate the widget bounds for iPad popup position
Rect get bounds {
final referenceBox =
childKey.currentContext!.findRenderObject() as RenderBox;
final topLeft =
referenceBox.localToGlobal(referenceBox.paintBounds.topLeft);
final bottomRight =
referenceBox.localToGlobal(referenceBox.paintBounds.bottomRight);
return Rect.fromPoints(topLeft, bottomRight);
}
}
/// Action to add the the [PdfPreview] widget
class PdfPreviewAction extends StatelessWidget {
/// Represents an icon to add to [PdfPreview]
const PdfPreviewAction({
Key? key,
required this.icon,
required this.onPressed,
}) : super(key: key);
/// The icon to display
final Widget icon;
/// The callback called when the user tap on the icon
final OnPdfPreviewActionPressed? onPressed;
@override
Widget build(BuildContext context) {
return IconButton(
icon: icon,
onPressed: onPressed == null ? null : () => pressed(context),
);
}
Future<void> pressed(BuildContext context) async {
final data = PdfPreviewController.of(context);
onPressed!(context, data.buildDocument, data.pageFormat);
}
}
class PdfPrintAction extends StatelessWidget {
const PdfPrintAction({
Key? key,
Widget? icon,
String? jobName,
this.onPrinted,
this.onPrintError,
this.dynamicLayout = true,
this.usePrinterSettings = false,
}) : icon = icon ?? const Icon(Icons.print),
jobName = jobName ?? 'Document',
super(key: key);
final Widget icon;
final String jobName;
/// Request page re-layout to match the printer paper and margins.
/// Mitigate an issue with iOS and macOS print dialog that prevent any
/// channel message while opened.
final bool dynamicLayout;
/// Set [usePrinterSettings] to true to use the configuration defined by
/// the printer. May not work for all the printers and can depend on the
/// drivers. (Supported platforms: Windows)
final bool usePrinterSettings;
/// Called if the user prints the pdf document
final VoidCallback? onPrinted;
/// Called if an error creating the Pdf occured
final void Function(dynamic error)? onPrintError;
@override
Widget build(BuildContext context) {
return PdfPreviewAction(
icon: icon,
onPressed: _print,
);
}
Future<void> _print(
BuildContext context,
LayoutCallback build,
PdfPageFormat pageFormat,
) async {
final data = PdfPreviewController.of(context);
try {
final result = await Printing.layoutPdf(
onLayout: build,
name: jobName,
format: data.actualPageFormat,
dynamicLayout: dynamicLayout,
usePrinterSettings: usePrinterSettings,
);
if (result) {
onPrinted?.call();
}
} catch (exception, stack) {
InformationCollector? collector;
assert(() {
collector = () sync* {
yield StringProperty('PageFormat', data.actualPageFormat.toString());
};
return true;
}());
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'printing',
context: ErrorDescription('while printing a PDF'),
informationCollector: collector,
));
onPrintError?.call(exception);
}
}
}
class PdfShareAction extends StatelessWidget with PdfPreviewActionBounds {
PdfShareAction({
Key? key,
Widget? icon,
String? filename,
this.subject,
this.body,
this.emails,
this.onShared,
this.onShareError,
}) : icon = icon ?? const Icon(Icons.share),
filename = filename ?? 'document.pdf',
super(key: key);
final Widget icon;
final String filename;
/// email subject when email application is selected from the share dialog
final String? subject;
/// extra text to share with Pdf document
final String? body;
/// list of email addresses which will be filled automatically if the email application
/// is selected from the share dialog.
/// This will work only for Android platform.
final List<String>? emails;
/// Called if the user prints the pdf document
final VoidCallback? onShared;
/// Called if an error creating the Pdf occured
final void Function(dynamic error)? onShareError;
@override
Widget build(BuildContext context) {
return PdfPreviewAction(
key: childKey,
icon: icon,
onPressed: _share,
);
}
Future<void> _share(
BuildContext context,
LayoutCallback build,
PdfPageFormat pageFormat,
) async {
final bytes = await build(pageFormat);
final result = await Printing.sharePdf(
bytes: bytes,
bounds: bounds,
filename: filename,
body: body,
subject: subject,
emails: emails,
);
if (result) {
onShared?.call();
}
}
}
class PdfPageFormatAction extends StatelessWidget {
const PdfPageFormatAction({
Key? key,
required this.pageFormats,
}) : super(key: key);
/// List of page formats the user can choose
final Map<String, PdfPageFormat> pageFormats;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final iconColor = theme.primaryIconTheme.color ?? Colors.white;
final data = PdfPreviewController.listen(context);
final keys = pageFormats.keys.toList();
return DropdownButton<PdfPageFormat>(
dropdownColor: theme.primaryColor,
icon: Icon(
Icons.arrow_drop_down,
color: iconColor,
),
value: data.pageFormat,
items: List<DropdownMenuItem<PdfPageFormat>>.generate(
pageFormats.length,
(int index) {
final key = keys[index];
final val = pageFormats[key];
return DropdownMenuItem<PdfPageFormat>(
value: val,
child: Text(key, style: TextStyle(color: iconColor)),
);
},
),
onChanged: (PdfPageFormat? pageFormat) {
if (pageFormat != null) {
data.pageFormat = pageFormat;
}
},
);
}
}
class PdfPageOrientationAction extends StatelessWidget {
const PdfPageOrientationAction({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final iconColor = theme.primaryIconTheme.color ?? Colors.white;
final data = PdfPreviewController.listen(context);
final horizontal = data.horizontal;
final disabledColor = iconColor.withAlpha(120);
return ToggleButtons(
renderBorder: false,
borderColor: disabledColor,
color: disabledColor,
selectedBorderColor: iconColor,
selectedColor: iconColor,
onPressed: (int index) {
data.horizontal = index == 1;
},
isSelected: <bool>[horizontal == false, horizontal == true],
children: <Widget>[
Transform.rotate(
angle: -math.pi / 2,
child: const Icon(
Icons.note_outlined,
),
),
const Icon(Icons.note_outlined),
],
);
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import '../callback.dart';
mixin PdfPreviewData implements ChangeNotifier {
// PdfPreviewData(this.build);
LayoutCallback get buildDocument;
PdfPageFormat? _pageFormat;
PdfPageFormat get initialPageFormat;
PdfPageFormat get pageFormat => _pageFormat ?? initialPageFormat;
set pageFormat(PdfPageFormat value) {
if (_pageFormat == value) {
return;
}
_pageFormat = value;
notifyListeners();
}
bool? _horizontal;
/// Is the print horizontal
bool get horizontal => _horizontal ?? pageFormat.width > pageFormat.height;
set horizontal(bool value) {
if (_horizontal == value) {
return;
}
_horizontal = value;
notifyListeners();
}
/// Computed page format
PdfPageFormat get computedPageFormat =>
horizontal ? pageFormat.landscape : pageFormat.portrait;
String get localPageFormat {
final locale = WidgetsBinding.instance!.window.locale;
// ignore: unnecessary_cast
final cc = (locale as Locale?)?.countryCode ?? 'US';
if (cc == 'US' || cc == 'CA' || cc == 'MX') {
return 'Letter';
}
return 'A4';
}
PdfPageFormat get actualPageFormat => pageFormat;
}
class PdfPreviewController extends InheritedNotifier {
const PdfPreviewController({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child, notifier: data);
final PdfPreviewData data;
static PdfPreviewData of(BuildContext context) {
final result =
context.findAncestorWidgetOfExactType<PdfPreviewController>();
assert(result != null, 'No PdfPreview found in context');
return result!.data;
}
static PdfPreviewData listen(BuildContext context) {
final result =
context.dependOnInheritedWidgetOfExactType<PdfPreviewController>();
assert(result != null, 'No PdfPreview found in context');
return result!.data;
}
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return false;
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import '../callback.dart';
import '../printing.dart';
import '../printing_info.dart';
import 'raster.dart';
/// Flutter widget that uses the rasterized pdf pages to display a document.
class PdfPreviewCustom extends StatefulWidget {
/// Show a pdf document built on demand
const PdfPreviewCustom({
Key? key,
this.pageFormat = PdfPageFormat.a4,
required this.build,
this.maxPageWidth,
this.onError,
this.scrollViewDecoration,
this.pdfPreviewPageDecoration,
this.pages,
this.previewPageMargin,
this.padding,
this.shouldRepaint = false,
this.loadingWidget,
}) : super(key: key);
/// Pdf paper page format
final PdfPageFormat pageFormat;
/// Called when a pdf document is needed
final LayoutCallback build;
/// Maximum width of the pdf document on screen
final double? maxPageWidth;
/// Widget to display if the PDF document cannot be displayed
final Widget Function(BuildContext context, Object error)? onError;
/// Decoration of scrollView
final Decoration? scrollViewDecoration;
/// Decoration of PdfPreviewPage
final Decoration? pdfPreviewPageDecoration;
/// Pages to display. Default will display all the pages.
final List<int>? pages;
/// margin for the document preview page
///
/// defaults to [EdgeInsets.only(left: 20, top: 8, right: 20, bottom: 12)],
final EdgeInsets? previewPageMargin;
/// padding for the pdf_preview widget
final EdgeInsets? padding;
/// Force repainting the PDF document
final bool shouldRepaint;
/// Custom loading widget to use that is shown while PDF is being generated.
/// If null, a [CircularProgressIndicator] is used instead.
final Widget? loadingWidget;
@override
PdfPreviewCustomState createState() => PdfPreviewCustomState();
}
class PdfPreviewCustomState extends State<PdfPreviewCustom>
with PdfPreviewRaster {
final listView = GlobalKey();
bool infoLoaded = false;
int? preview;
double? updatePosition;
final scrollController = ScrollController(
keepScrollOffset: true,
);
final transformationController = TransformationController();
Timer? previewUpdate;
static const _errorMessage = 'Unable to display the document';
@override
void dispose() {
previewUpdate?.cancel();
super.dispose();
}
@override
void reassemble() {
raster();
super.reassemble();
}
@override
void didUpdateWidget(covariant PdfPreviewCustom oldWidget) {
if (oldWidget.build != widget.build ||
widget.shouldRepaint ||
widget.pageFormat != oldWidget.pageFormat) {
preview = null;
updatePosition = null;
raster();
}
super.didUpdateWidget(oldWidget);
}
@override
void didChangeDependencies() {
if (!infoLoaded) {
infoLoaded = true;
Printing.info().then((PrintingInfo _info) {
setState(() {
info = _info;
raster();
});
});
}
raster();
super.didChangeDependencies();
}
Widget _showError(Object error) {
if (widget.onError != null) {
return widget.onError!(context, error);
}
return ErrorWidget(error);
}
Widget _createPreview() {
if (error != null) {
return _showError(error!);
}
final _info = info;
if (_info != null && !_info.canRaster) {
return _showError(_errorMessage);
}
if (pages.isEmpty) {
return widget.loadingWidget ??
const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
controller: scrollController,
padding: widget.padding,
itemCount: pages.length,
itemBuilder: (BuildContext context, int index) => GestureDetector(
onDoubleTap: () {
setState(() {
updatePosition = scrollController.position.pixels;
preview = index;
transformationController.value.setIdentity();
});
},
child: pages[index],
),
);
}
Widget _zoomPreview() {
return GestureDetector(
onDoubleTap: () {
setState(() {
preview = null;
});
},
child: InteractiveViewer(
transformationController: transformationController,
maxScale: 5,
child: Center(child: pages[preview!]),
),
);
}
@override
Widget build(BuildContext context) {
Widget page;
if (preview != null) {
page = _zoomPreview();
} else {
page = Container(
constraints: widget.maxPageWidth != null
? BoxConstraints(maxWidth: widget.maxPageWidth!)
: null,
child: _createPreview(),
);
if (updatePosition != null) {
Timer.run(() {
scrollController.jumpTo(updatePosition!);
updatePosition = null;
});
}
}
return Container(
decoration: widget.scrollViewDecoration ??
BoxDecoration(
gradient: LinearGradient(
colors: <Color>[Colors.grey.shade400, Colors.grey.shade200],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
width: double.infinity,
alignment: Alignment.center,
child: page,
);
}
}
... ...
... ... @@ -14,10 +14,6 @@
* limitations under the License.
*/
import 'dart:async';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
... ... @@ -25,8 +21,11 @@ import 'package:pdf/widgets.dart' as pw;
import '../callback.dart';
import '../printing.dart';
import '../printing_info.dart';
import 'pdf_preview_action.dart';
import 'pdf_preview_raster.dart';
import 'actions.dart';
import 'controller.dart';
import 'custom.dart';
export 'custom.dart';
/// Flutter widget that uses the rasterized pdf pages to display a document.
class PdfPreview extends StatefulWidget {
... ... @@ -115,7 +114,7 @@ class PdfPreview extends StatefulWidget {
/// Decoration of scrollView
final Decoration? scrollViewDecoration;
/// Decoration of _PdfPreviewPage
/// Decoration of PdfPreviewPage
final Decoration? pdfPreviewPageDecoration;
/// Name of the PDF when sharing. It must include the extension.
... ... @@ -159,89 +158,64 @@ class PdfPreview extends StatefulWidget {
_PdfPreviewState createState() => _PdfPreviewState();
}
class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
final GlobalKey<State<StatefulWidget>> shareWidget = GlobalKey();
final GlobalKey<State<StatefulWidget>> listView = GlobalKey();
class _PdfPreviewState extends State<PdfPreview>
with PdfPreviewData, ChangeNotifier {
final previewWidget = GlobalKey<PdfPreviewCustomState>();
PdfPageFormat? _pageFormat;
/// Printing subsystem information
PrintingInfo? info;
var infoLoaded = false;
String get localPageFormat {
final locale = WidgetsBinding.instance!.window.locale;
// ignore: unnecessary_cast
final cc = (locale as Locale?)?.countryCode ?? 'US';
@override
LayoutCallback get buildDocument => widget.build;
if (cc == 'US' || cc == 'CA' || cc == 'MX') {
return 'Letter';
}
return 'A4';
}
@override
PdfPageFormat get initialPageFormat =>
widget.initialPageFormat ??
(widget.pageFormats.isNotEmpty
? (widget.pageFormats[localPageFormat] ??
widget.pageFormats.values.first)
: (PdfPreview._defaultPageFormats[localPageFormat]) ??
PdfPreview._defaultPageFormats.values.first);
@override
PdfPageFormat get pageFormat {
_pageFormat ??= widget.initialPageFormat == null
? widget.pageFormats[localPageFormat]
: _pageFormat = widget.initialPageFormat!;
var _pageFormat = super.pageFormat;
if (!widget.pageFormats.containsValue(_pageFormat)) {
_pageFormat = widget.initialPageFormat ??
(widget.pageFormats.isNotEmpty
? widget.pageFormats.values.first
: PdfPreview._defaultPageFormats[localPageFormat]);
_pageFormat = initialPageFormat;
pageFormat = _pageFormat;
}
return _pageFormat!;
return _pageFormat;
}
bool infoLoaded = false;
int? preview;
double? updatePosition;
final scrollController = ScrollController(
keepScrollOffset: true,
);
final transformationController = TransformationController();
Timer? previewUpdate;
static const _errorMessage = 'Unable to display the document';
@override
void initState() {
if (widget.initialPageFormat == null) {
final locale = WidgetsBinding.instance!.window.locale;
// ignore: unnecessary_cast
final cc = (locale as Locale?)?.countryCode ?? 'US';
if (cc == 'US' || cc == 'CA' || cc == 'MX') {
_pageFormat = PdfPageFormat.letter;
} else {
_pageFormat = PdfPageFormat.a4;
}
} else {
_pageFormat = widget.initialPageFormat!;
}
PdfPageFormat get actualPageFormat {
var format = pageFormat;
final pages = previewWidget.currentState?.pages ?? const [];
final dpi = previewWidget.currentState?.dpi ?? 72;
final _pageFormats = widget.pageFormats;
if (!_pageFormats.containsValue(pageFormat)) {
_pageFormat = _pageFormats.values.first;
if (!widget.canChangePageFormat && pages.isNotEmpty) {
format = PdfPageFormat(
pages.first.page!.width * 72 / dpi,
pages.first.page!.height * 72 / dpi,
marginAll: 5 * PdfPageFormat.mm,
);
}
super.initState();
return format;
}
@override
void dispose() {
previewUpdate?.cancel();
super.dispose();
void initState() {
addListener(() {
if (mounted) {
setState(() {});
}
});
@override
void reassemble() {
raster();
super.reassemble();
super.initState();
}
@override
... ... @@ -249,10 +223,7 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
if (oldWidget.build != widget.build ||
widget.shouldRepaint ||
widget.pageFormats != oldWidget.pageFormats) {
preview = null;
updatePosition = null;
raster();
setState(() {});
}
super.didUpdateWidget(oldWidget);
}
... ... @@ -264,208 +235,51 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
Printing.info().then((PrintingInfo _info) {
setState(() {
info = _info;
raster();
});
});
}
raster();
super.didChangeDependencies();
}
Widget _showError(Object error) {
if (widget.onError != null) {
return widget.onError!(context, error);
}
return ErrorWidget(error);
}
Widget _createPreview() {
if (error != null) {
return _showError(error!);
}
final _info = info;
if (_info != null && !_info.canRaster) {
return _showError(_errorMessage);
}
if (pages.isEmpty) {
return widget.loadingWidget ??
const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
controller: scrollController,
padding: widget.padding,
itemCount: pages.length,
itemBuilder: (BuildContext context, int index) => GestureDetector(
onDoubleTap: () {
setState(() {
updatePosition = scrollController.position.pixels;
preview = index;
transformationController.value.setIdentity();
});
},
child: pages[index],
),
);
}
Widget _zoomPreview() {
return GestureDetector(
onDoubleTap: () {
setState(() {
preview = null;
});
},
child: InteractiveViewer(
transformationController: transformationController,
maxScale: 5,
child: Center(child: pages[preview!]),
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final iconColor = theme.primaryIconTheme.color ?? Colors.white;
Widget page;
if (preview != null) {
page = _zoomPreview();
} else {
page = Container(
constraints: widget.maxPageWidth != null
? BoxConstraints(maxWidth: widget.maxPageWidth!)
: null,
child: _createPreview(),
);
if (updatePosition != null) {
Timer.run(() {
scrollController.jumpTo(updatePosition!);
updatePosition = null;
});
}
}
final Widget scrollView = Container(
decoration: widget.scrollViewDecoration ??
BoxDecoration(
gradient: LinearGradient(
colors: <Color>[Colors.grey.shade400, Colors.grey.shade200],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
width: double.infinity,
alignment: Alignment.center,
child: page,
);
final actions = <Widget>[];
if (widget.allowPrinting && info?.canPrint == true) {
actions.add(
IconButton(
icon: const Icon(Icons.print),
onPressed: _print,
),
);
actions.add(PdfPrintAction(
jobName: widget.pdfFileName,
dynamicLayout: widget.dynamicLayout,
onPrinted:
widget.onPrinted == null ? null : () => widget.onPrinted!(context),
onPrintError: widget.onPrintError == null
? null
: (dynamic error) => widget.onPrintError!(context, error),
));
}
if (widget.allowSharing && info?.canShare == true) {
actions.add(
IconButton(
key: shareWidget,
icon: const Icon(Icons.share),
onPressed: _share,
),
);
actions.add(PdfShareAction(
filename: widget.pdfFileName,
onShared:
widget.onPrinted == null ? null : () => widget.onPrinted!(context),
));
}
if (widget.canChangePageFormat) {
final keys = widget.pageFormats.keys.toList();
actions.add(
DropdownButton<PdfPageFormat>(
dropdownColor: theme.primaryColor,
icon: Icon(
Icons.arrow_drop_down,
color: iconColor,
),
value: pageFormat,
items: List<DropdownMenuItem<PdfPageFormat>>.generate(
widget.pageFormats.length,
(int index) {
final key = keys[index];
final val = widget.pageFormats[key];
return DropdownMenuItem<PdfPageFormat>(
value: val,
child: Text(key, style: TextStyle(color: iconColor)),
);
},
),
onChanged: (PdfPageFormat? pageFormat) {
setState(() {
if (pageFormat != null) {
_pageFormat = pageFormat;
raster();
}
});
},
),
);
actions.add(PdfPageFormatAction(
pageFormats: widget.pageFormats,
));
if (widget.canChangeOrientation) {
horizontal ??= pageFormat.width > pageFormat.height;
final disabledColor = iconColor.withAlpha(120);
actions.add(
ToggleButtons(
renderBorder: false,
borderColor: disabledColor,
color: disabledColor,
selectedBorderColor: iconColor,
selectedColor: iconColor,
onPressed: (int index) {
setState(() {
horizontal = index == 1;
raster();
});
},
isSelected: <bool>[horizontal == false, horizontal == true],
children: <Widget>[
Transform.rotate(
angle: -pi / 2, child: const Icon(Icons.note_outlined)),
const Icon(Icons.note_outlined),
],
),
);
actions.add(const PdfPageOrientationAction());
}
}
if (widget.actions != null) {
for (final action in widget.actions!) {
actions.add(
IconButton(
icon: action.icon,
onPressed: action.onPressed == null
? null
: () => action.onPressed!(
context,
widget.build,
computedPageFormat,
),
),
);
}
}
widget.actions?.forEach(actions.add);
assert(() {
if (actions.isNotEmpty && widget.canDebug) {
... ... @@ -474,12 +288,10 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
activeColor: Colors.red,
value: pw.Document.debug,
onChanged: (bool value) {
setState(
() {
setState(() {
pw.Document.debug = value;
raster();
},
);
});
previewWidget.currentState?.raster();
},
),
);
... ... @@ -488,10 +300,27 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
return true;
}());
return Column(
return PdfPreviewController(
data: this,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: scrollView),
Expanded(
child: PdfPreviewCustom(
key: previewWidget,
build: widget.build,
loadingWidget: widget.loadingWidget,
maxPageWidth: widget.maxPageWidth,
onError: widget.onError,
padding: widget.padding,
pageFormat: computedPageFormat,
pages: widget.pages,
pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration,
previewPageMargin: widget.previewPageMargin,
scrollViewDecoration: widget.scrollViewDecoration,
shouldRepaint: widget.shouldRepaint,
),
),
if (actions.isNotEmpty && widget.useActions)
IconTheme.merge(
data: IconThemeData(
... ... @@ -510,79 +339,9 @@ class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
),
),
),
)
),
],
),
);
}
Future<void> _print() async {
var format = computedPageFormat;
if (!widget.canChangePageFormat && pages.isNotEmpty) {
format = PdfPageFormat(
pages.first.page!.width * 72 / dpi,
pages.first.page!.height * 72 / dpi,
marginAll: 5 * PdfPageFormat.mm,
);
}
try {
final result = await Printing.layoutPdf(
onLayout: widget.build,
name: widget.pdfFileName ?? 'Document',
format: format,
dynamicLayout: widget.dynamicLayout,
);
if (result && widget.onPrinted != null) {
widget.onPrinted!(context);
}
} catch (exception, stack) {
InformationCollector? collector;
assert(() {
collector = () sync* {
yield StringProperty('PageFormat', computedPageFormat.toString());
};
return true;
}());
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'printing',
context: ErrorDescription('while printing a PDF'),
informationCollector: collector,
));
if (widget.onPrintError != null) {
widget.onPrintError!(context, exception);
}
}
}
Future<void> _share() async {
// Calculate the widget center for iPad sharing popup position
final referenceBox =
shareWidget.currentContext!.findRenderObject() as RenderBox;
final topLeft =
referenceBox.localToGlobal(referenceBox.paintBounds.topLeft);
final bottomRight =
referenceBox.localToGlobal(referenceBox.paintBounds.bottomRight);
final bounds = Rect.fromPoints(topLeft, bottomRight);
final bytes = await widget.build(computedPageFormat);
final result = await Printing.sharePdf(
bytes: bytes,
bounds: bounds,
filename: widget.pdfFileName ?? 'document.pdf',
body: widget.shareActionExtraBody,
subject: widget.shareActionExtraSubject,
emails: widget.shareActionExtraEmails,
);
if (result && widget.onShared != null) {
widget.onShared!(context);
}
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import '../callback.dart';
/// Base Action callback
typedef OnPdfPreviewActionPressed = void Function(
BuildContext context,
LayoutCallback build,
PdfPageFormat pageFormat,
);
/// Action to add the the [PdfPreview] widget
class PdfPreviewAction {
/// Represents an icon to add to [PdfPreview]
const PdfPreviewAction({
required this.icon,
required this.onPressed,
});
/// The icon to display
final Icon icon;
/// The callback called when the user tap on the icon
final OnPdfPreviewActionPressed? onPressed;
}
... ... @@ -25,18 +25,15 @@ import 'package:pdf/pdf.dart';
import '../printing.dart';
import '../printing_info.dart';
import '../raster.dart';
import 'pdf_preview.dart';
import 'pdf_preview_page.dart';
import 'custom.dart';
import 'page.dart';
/// Raster PDF documents
mixin PdfPreviewRaster on State<PdfPreview> {
mixin PdfPreviewRaster on State<PdfPreviewCustom> {
static const _updateTime = Duration(milliseconds: 300);
/// Configured page format
PdfPageFormat get pageFormat;
/// Is the print horizontal
bool? horizontal;
PdfPageFormat get pageFormat => widget.pageFormat;
/// Resulting pages
final List<PdfPreviewPage> pages = <PdfPreviewPage>[];
... ... @@ -60,11 +57,6 @@ mixin PdfPreviewRaster on State<PdfPreview> {
super.dispose();
}
/// Computed page format
PdfPageFormat get computedPageFormat => horizontal != null
? (horizontal! ? pageFormat.landscape : pageFormat.portrait)
: pageFormat;
/// Rasterize the document
void raster() {
_previewUpdate?.cancel();
... ... @@ -72,7 +64,7 @@ mixin PdfPreviewRaster on State<PdfPreview> {
final mq = MediaQuery.of(context);
dpi = (min(mq.size.width - 16, widget.maxPageWidth ?? double.infinity)) *
mq.devicePixelRatio /
computedPageFormat.width *
pageFormat.width *
72;
_raster();
... ... @@ -107,13 +99,13 @@ mixin PdfPreviewRaster on State<PdfPreview> {
}
try {
_doc = await widget.build(computedPageFormat);
_doc = await widget.build(pageFormat);
} catch (exception, stack) {
InformationCollector? collector;
assert(() {
collector = () sync* {
yield StringProperty('PageFormat', computedPageFormat.toString());
yield StringProperty('PageFormat', pageFormat.toString());
};
return true;
}());
... ... @@ -174,7 +166,7 @@ mixin PdfPreviewRaster on State<PdfPreview> {
assert(() {
collector = () sync* {
yield StringProperty('PageFormat', computedPageFormat.toString());
yield StringProperty('PageFormat', pageFormat.toString());
};
return true;
}());
... ...