David PHAM-VAN

Improve PdfPreview widget

1 # Changelog 1 # Changelog
2 2
3 -## 5.2.2 3 +## 5.3.0
4 4
5 - Fix raster crash on all OS. 5 - Fix raster crash on all OS.
  6 +- Improve PdfPreview widget
6 7
7 ## 5.2.1 8 ## 5.2.1
8 9
@@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
16 16
17 export 'src/asset_utils.dart'; 17 export 'src/asset_utils.dart';
18 export 'src/callback.dart'; 18 export 'src/callback.dart';
19 -export 'src/pdf_preview.dart'; 19 +export 'src/preview/pdf_preview.dart';
  20 +export 'src/preview/pdf_preview_action.dart';
20 export 'src/printer.dart'; 21 export 'src/printer.dart';
21 export 'src/printing.dart'; 22 export 'src/printing.dart';
22 export 'src/printing_info.dart'; 23 export 'src/printing_info.dart';
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
1 import 'dart:async'; 17 import 'dart:async';
2 import 'dart:math'; 18 import 'dart:math';
3 -import 'dart:typed_data';  
4 19
5 import 'package:flutter/foundation.dart'; 20 import 'package:flutter/foundation.dart';
6 import 'package:flutter/material.dart'; 21 import 'package:flutter/material.dart';
7 import 'package:pdf/pdf.dart'; 22 import 'package:pdf/pdf.dart';
8 import 'package:pdf/widgets.dart' as pw; 23 import 'package:pdf/widgets.dart' as pw;
9 24
10 -import 'callback.dart';  
11 -import 'printing.dart';  
12 -import 'printing_info.dart';  
13 -import 'raster.dart'; 25 +import '../callback.dart';
  26 +import '../printing.dart';
  27 +import '../printing_info.dart';
  28 +import 'pdf_preview_action.dart';
  29 +import 'pdf_preview_raster.dart';
14 30
15 /// Flutter widget that uses the rasterized pdf pages to display a document. 31 /// Flutter widget that uses the rasterized pdf pages to display a document.
16 class PdfPreview extends StatefulWidget { 32 class PdfPreview extends StatefulWidget {
@@ -25,7 +41,7 @@ class PdfPreview extends StatefulWidget { @@ -25,7 +41,7 @@ class PdfPreview extends StatefulWidget {
25 this.canChangePageFormat = true, 41 this.canChangePageFormat = true,
26 this.canChangeOrientation = true, 42 this.canChangeOrientation = true,
27 this.actions, 43 this.actions,
28 - this.pageFormats, 44 + this.pageFormats = _defaultPageFormats,
29 this.onError, 45 this.onError,
30 this.onPrinted, 46 this.onPrinted,
31 this.onPrintError, 47 this.onPrintError,
@@ -44,6 +60,11 @@ class PdfPreview extends StatefulWidget { @@ -44,6 +60,11 @@ class PdfPreview extends StatefulWidget {
44 this.shouldRepaint = false, 60 this.shouldRepaint = false,
45 }) : super(key: key); 61 }) : super(key: key);
46 62
  63 + static const _defaultPageFormats = <String, PdfPageFormat>{
  64 + 'A4': PdfPageFormat.a4,
  65 + 'Letter': PdfPageFormat.letter,
  66 + };
  67 +
47 /// Called when a pdf document is needed 68 /// Called when a pdf document is needed
48 final LayoutCallback build; 69 final LayoutCallback build;
49 70
@@ -72,10 +93,10 @@ class PdfPreview extends StatefulWidget { @@ -72,10 +93,10 @@ class PdfPreview extends StatefulWidget {
72 final List<PdfPreviewAction>? actions; 93 final List<PdfPreviewAction>? actions;
73 94
74 /// List of page formats the user can choose 95 /// List of page formats the user can choose
75 - final Map<String, PdfPageFormat>? pageFormats; 96 + final Map<String, PdfPageFormat> pageFormats;
76 97
77 /// Widget to display if the PDF document cannot be displayed 98 /// Widget to display if the PDF document cannot be displayed
78 - final Widget Function(BuildContext context)? onError; 99 + final Widget Function(BuildContext context, Object error)? onError;
79 100
80 /// Called if the user prints the pdf document 101 /// Called if the user prints the pdf document
81 final void Function(BuildContext context)? onPrinted; 102 final void Function(BuildContext context)? onPrinted;
@@ -129,22 +150,40 @@ class PdfPreview extends StatefulWidget { @@ -129,22 +150,40 @@ class PdfPreview extends StatefulWidget {
129 _PdfPreviewState createState() => _PdfPreviewState(); 150 _PdfPreviewState createState() => _PdfPreviewState();
130 } 151 }
131 152
132 -class _PdfPreviewState extends State<PdfPreview> { 153 +class _PdfPreviewState extends State<PdfPreview> with PdfPreviewRaster {
133 final GlobalKey<State<StatefulWidget>> shareWidget = GlobalKey(); 154 final GlobalKey<State<StatefulWidget>> shareWidget = GlobalKey();
134 final GlobalKey<State<StatefulWidget>> listView = GlobalKey(); 155 final GlobalKey<State<StatefulWidget>> listView = GlobalKey();
135 156
136 - final List<_PdfPreviewPage> pages = <_PdfPreviewPage>[]; 157 + PdfPageFormat? _pageFormat;
137 158
138 - late PdfPageFormat pageFormat; 159 + String get localPageFormat {
  160 + final locale = WidgetsBinding.instance!.window.locale;
  161 + // ignore: unnecessary_cast
  162 + final cc = (locale as Locale?)?.countryCode ?? 'US';
139 163
140 - bool? horizontal; 164 + if (cc == 'US' || cc == 'CA' || cc == 'MX') {
  165 + return 'Letter';
  166 + }
  167 + return 'A4';
  168 + }
141 169
142 - PrintingInfo info = PrintingInfo.unavailable;  
143 - bool infoLoaded = false; 170 + @override
  171 + PdfPageFormat get pageFormat {
  172 + _pageFormat ??= widget.initialPageFormat == null
  173 + ? widget.pageFormats[localPageFormat]
  174 + : _pageFormat = widget.initialPageFormat!;
  175 +
  176 + if (!widget.pageFormats.containsValue(_pageFormat)) {
  177 + _pageFormat = widget.initialPageFormat ??
  178 + (widget.pageFormats.isNotEmpty
  179 + ? widget.pageFormats.values.first
  180 + : PdfPreview._defaultPageFormats[localPageFormat]);
  181 + }
144 182
145 - double dpi = 10; 183 + return _pageFormat!;
  184 + }
146 185
147 - Object? error; 186 + bool infoLoaded = false;
148 187
149 int? preview; 188 int? preview;
150 189
@@ -158,125 +197,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -158,125 +197,7 @@ class _PdfPreviewState extends State<PdfPreview> {
158 197
159 Timer? previewUpdate; 198 Timer? previewUpdate;
160 199
161 - var _rastering = false;  
162 -  
163 - static const defaultPageFormats = <String, PdfPageFormat>{  
164 - 'A4': PdfPageFormat.a4,  
165 - 'Letter': PdfPageFormat.letter,  
166 - };  
167 -  
168 - PdfPageFormat get computedPageFormat => horizontal != null  
169 - ? (horizontal! ? pageFormat.landscape : pageFormat.portrait)  
170 - : pageFormat;  
171 -  
172 - Future<void> _raster() async {  
173 - if (_rastering) {  
174 - return;  
175 - }  
176 - _rastering = true;  
177 -  
178 - Uint8List _doc;  
179 -  
180 - if (!info.canRaster) {  
181 - assert(() {  
182 - if (kIsWeb) {  
183 - FlutterError.reportError(FlutterErrorDetails(  
184 - exception: Exception(  
185 - 'Unable to find the `pdf.js` library.\nPlease follow the installation instructions at https://github.com/DavBfr/dart_pdf/tree/master/printing#installing'),  
186 - library: 'printing',  
187 - context: ErrorDescription('while rendering a PDF'),  
188 - ));  
189 - }  
190 -  
191 - return true;  
192 - }());  
193 -  
194 - _rastering = false;  
195 - return;  
196 - }  
197 -  
198 - try {  
199 - _doc = await widget.build(computedPageFormat);  
200 - } catch (exception, stack) {  
201 - InformationCollector? collector;  
202 -  
203 - assert(() {  
204 - collector = () sync* {  
205 - yield StringProperty('PageFormat', computedPageFormat.toString());  
206 - };  
207 - return true;  
208 - }());  
209 -  
210 - FlutterError.reportError(FlutterErrorDetails(  
211 - exception: exception,  
212 - stack: stack,  
213 - library: 'printing',  
214 - context: ErrorDescription('while generating a PDF'),  
215 - informationCollector: collector,  
216 - ));  
217 - error = exception;  
218 - _rastering = false;  
219 - return;  
220 - }  
221 -  
222 - if (error != null) {  
223 - setState(() {  
224 - error = null;  
225 - });  
226 - }  
227 -  
228 - try {  
229 - var pageNum = 0;  
230 - await for (final PdfRaster page in Printing.raster(  
231 - _doc,  
232 - dpi: dpi,  
233 - pages: widget.pages,  
234 - )) {  
235 - if (!mounted) {  
236 - _rastering = false;  
237 - return;  
238 - }  
239 - setState(() {  
240 - if (pages.length <= pageNum) {  
241 - pages.add(_PdfPreviewPage(  
242 - page: page,  
243 - pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration,  
244 - pageMargin: widget.previewPageMargin,  
245 - ));  
246 - } else {  
247 - pages[pageNum] = _PdfPreviewPage(  
248 - page: page,  
249 - pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration,  
250 - pageMargin: widget.previewPageMargin,  
251 - );  
252 - }  
253 - });  
254 -  
255 - pageNum++;  
256 - pages.removeRange(pageNum, pages.length);  
257 - }  
258 - } catch (exception, stack) {  
259 - InformationCollector? collector;  
260 -  
261 - assert(() {  
262 - collector = () sync* {  
263 - yield StringProperty('PageFormat', computedPageFormat.toString());  
264 - };  
265 - return true;  
266 - }());  
267 -  
268 - FlutterError.reportError(FlutterErrorDetails(  
269 - exception: exception,  
270 - stack: stack,  
271 - library: 'printing',  
272 - context: ErrorDescription('while generating a PDF'),  
273 - informationCollector: collector,  
274 - ));  
275 - error = exception;  
276 - }  
277 -  
278 - _rastering = false;  
279 - } 200 + static const _errorMessage = 'Unable to display the document';
280 201
281 @override 202 @override
282 void initState() { 203 void initState() {
@@ -286,17 +207,17 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -286,17 +207,17 @@ class _PdfPreviewState extends State<PdfPreview> {
286 final cc = (locale as Locale?)?.countryCode ?? 'US'; 207 final cc = (locale as Locale?)?.countryCode ?? 'US';
287 208
288 if (cc == 'US' || cc == 'CA' || cc == 'MX') { 209 if (cc == 'US' || cc == 'CA' || cc == 'MX') {
289 - pageFormat = PdfPageFormat.letter; 210 + _pageFormat = PdfPageFormat.letter;
290 } else { 211 } else {
291 - pageFormat = PdfPageFormat.a4; 212 + _pageFormat = PdfPageFormat.a4;
292 } 213 }
293 } else { 214 } else {
294 - pageFormat = widget.initialPageFormat!; 215 + _pageFormat = widget.initialPageFormat!;
295 } 216 }
296 217
297 - final _pageFormats = widget.pageFormats ?? defaultPageFormats; 218 + final _pageFormats = widget.pageFormats;
298 if (!_pageFormats.containsValue(pageFormat)) { 219 if (!_pageFormats.containsValue(pageFormat)) {
299 - pageFormat = _pageFormats.values.first; 220 + _pageFormat = _pageFormats.values.first;
300 } 221 }
301 222
302 super.initState(); 223 super.initState();
@@ -310,16 +231,19 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -310,16 +231,19 @@ class _PdfPreviewState extends State<PdfPreview> {
310 231
311 @override 232 @override
312 void reassemble() { 233 void reassemble() {
313 - _raster(); 234 + raster();
314 super.reassemble(); 235 super.reassemble();
315 } 236 }
316 237
317 @override 238 @override
318 void didUpdateWidget(covariant PdfPreview oldWidget) { 239 void didUpdateWidget(covariant PdfPreview oldWidget) {
319 - if (oldWidget.build != widget.build || widget.shouldRepaint) { 240 + if (oldWidget.build != widget.build ||
  241 + widget.shouldRepaint ||
  242 + widget.pageFormats != oldWidget.pageFormats) {
320 preview = null; 243 preview = null;
321 updatePosition = null; 244 updatePosition = null;
322 - _raster(); 245 +
  246 + raster();
323 } 247 }
324 super.didUpdateWidget(oldWidget); 248 super.didUpdateWidget(oldWidget);
325 } 249 }
@@ -327,54 +251,35 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -327,54 +251,35 @@ class _PdfPreviewState extends State<PdfPreview> {
327 @override 251 @override
328 void didChangeDependencies() { 252 void didChangeDependencies() {
329 if (!infoLoaded) { 253 if (!infoLoaded) {
  254 + infoLoaded = true;
330 Printing.info().then((PrintingInfo _info) { 255 Printing.info().then((PrintingInfo _info) {
331 setState(() { 256 setState(() {
332 - infoLoaded = true;  
333 info = _info; 257 info = _info;
  258 + raster();
334 }); 259 });
335 }); 260 });
336 } 261 }
337 262
338 - previewUpdate?.cancel();  
339 - previewUpdate = Timer(const Duration(seconds: 1), () {  
340 - final mq = MediaQuery.of(context);  
341 - dpi = (min(mq.size.width - 16, widget.maxPageWidth ?? double.infinity)) *  
342 - mq.devicePixelRatio /  
343 - computedPageFormat.width *  
344 - 72;  
345 -  
346 - _raster();  
347 - }); 263 + raster();
348 super.didChangeDependencies(); 264 super.didChangeDependencies();
349 } 265 }
350 266
351 - Widget _showError() { 267 + Widget _showError(Object error) {
352 if (widget.onError != null) { 268 if (widget.onError != null) {
353 - return widget.onError!(context); 269 + return widget.onError!(context, error);
354 } 270 }
355 271
356 - return const Center(  
357 - child: Text(  
358 - 'Unable to display the document',  
359 - style: TextStyle(  
360 - fontSize: 20,  
361 - ),  
362 - ),  
363 - ); 272 + return ErrorWidget(error);
364 } 273 }
365 274
366 Widget _createPreview() { 275 Widget _createPreview() {
367 if (error != null) { 276 if (error != null) {
368 - var content = _showError();  
369 - assert(() {  
370 - content = ErrorWidget(error!);  
371 - return true;  
372 - }());  
373 - return content; 277 + return _showError(error!);
374 } 278 }
375 279
376 - if (!info.canRaster) {  
377 - return _showError(); 280 + final _info = info;
  281 + if (_info != null && !_info.canRaster) {
  282 + return _showError(_errorMessage);
378 } 283 }
379 284
380 if (pages.isEmpty) { 285 if (pages.isEmpty) {
@@ -454,7 +359,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -454,7 +359,7 @@ class _PdfPreviewState extends State<PdfPreview> {
454 359
455 final actions = <Widget>[]; 360 final actions = <Widget>[];
456 361
457 - if (widget.allowPrinting && info.canPrint) { 362 + if (widget.allowPrinting && info?.canPrint == true) {
458 actions.add( 363 actions.add(
459 IconButton( 364 IconButton(
460 icon: const Icon(Icons.print), 365 icon: const Icon(Icons.print),
@@ -463,7 +368,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -463,7 +368,7 @@ class _PdfPreviewState extends State<PdfPreview> {
463 ); 368 );
464 } 369 }
465 370
466 - if (widget.allowSharing && info.canShare) { 371 + if (widget.allowSharing && info?.canShare == true) {
467 actions.add( 372 actions.add(
468 IconButton( 373 IconButton(
469 key: shareWidget, 374 key: shareWidget,
@@ -474,8 +379,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -474,8 +379,7 @@ class _PdfPreviewState extends State<PdfPreview> {
474 } 379 }
475 380
476 if (widget.canChangePageFormat) { 381 if (widget.canChangePageFormat) {
477 - final _pageFormats = widget.pageFormats ?? defaultPageFormats;  
478 - final keys = _pageFormats.keys.toList(); 382 + final keys = widget.pageFormats.keys.toList();
479 actions.add( 383 actions.add(
480 DropdownButton<PdfPageFormat>( 384 DropdownButton<PdfPageFormat>(
481 dropdownColor: theme.primaryColor, 385 dropdownColor: theme.primaryColor,
@@ -485,21 +389,21 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -485,21 +389,21 @@ class _PdfPreviewState extends State<PdfPreview> {
485 ), 389 ),
486 value: pageFormat, 390 value: pageFormat,
487 items: List<DropdownMenuItem<PdfPageFormat>>.generate( 391 items: List<DropdownMenuItem<PdfPageFormat>>.generate(
488 - _pageFormats.length, 392 + widget.pageFormats.length,
489 (int index) { 393 (int index) {
490 final key = keys[index]; 394 final key = keys[index];
491 - final val = _pageFormats[key]; 395 + final val = widget.pageFormats[key];
492 return DropdownMenuItem<PdfPageFormat>( 396 return DropdownMenuItem<PdfPageFormat>(
493 value: val, 397 value: val,
494 child: Text(key, style: TextStyle(color: iconColor)), 398 child: Text(key, style: TextStyle(color: iconColor)),
495 ); 399 );
496 }, 400 },
497 ), 401 ),
498 - onChanged: (PdfPageFormat? _pageFormat) { 402 + onChanged: (PdfPageFormat? pageFormat) {
499 setState(() { 403 setState(() {
500 - if (_pageFormat != null) {  
501 - pageFormat = _pageFormat;  
502 - _raster(); 404 + if (pageFormat != null) {
  405 + _pageFormat = pageFormat;
  406 + raster();
503 } 407 }
504 }); 408 });
505 }, 409 },
@@ -520,7 +424,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -520,7 +424,7 @@ class _PdfPreviewState extends State<PdfPreview> {
520 onPressed: (int index) { 424 onPressed: (int index) {
521 setState(() { 425 setState(() {
522 horizontal = index == 1; 426 horizontal = index == 1;
523 - _raster(); 427 + raster();
524 }); 428 });
525 }, 429 },
526 isSelected: <bool>[horizontal == false, horizontal == true], 430 isSelected: <bool>[horizontal == false, horizontal == true],
@@ -561,7 +465,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -561,7 +465,7 @@ class _PdfPreviewState extends State<PdfPreview> {
561 setState( 465 setState(
562 () { 466 () {
563 pw.Document.debug = value; 467 pw.Document.debug = value;
564 - _raster(); 468 + raster();
565 }, 469 },
566 ); 470 );
567 }, 471 },
@@ -624,9 +528,26 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -624,9 +528,26 @@ class _PdfPreviewState extends State<PdfPreview> {
624 if (result && widget.onPrinted != null) { 528 if (result && widget.onPrinted != null) {
625 widget.onPrinted!(context); 529 widget.onPrinted!(context);
626 } 530 }
627 - } catch (e) { 531 + } catch (exception, stack) {
  532 + InformationCollector? collector;
  533 +
  534 + assert(() {
  535 + collector = () sync* {
  536 + yield StringProperty('PageFormat', computedPageFormat.toString());
  537 + };
  538 + return true;
  539 + }());
  540 +
  541 + FlutterError.reportError(FlutterErrorDetails(
  542 + exception: exception,
  543 + stack: stack,
  544 + library: 'printing',
  545 + context: ErrorDescription('while printing a PDF'),
  546 + informationCollector: collector,
  547 + ));
  548 +
628 if (widget.onPrintError != null) { 549 if (widget.onPrintError != null) {
629 - widget.onPrintError!(context, e); 550 + widget.onPrintError!(context, exception);
630 } 551 }
631 } 552 }
632 } 553 }
@@ -656,76 +577,3 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -656,76 +577,3 @@ class _PdfPreviewState extends State<PdfPreview> {
656 } 577 }
657 } 578 }
658 } 579 }
659 -  
660 -class _PdfPreviewPage extends StatelessWidget {  
661 - const _PdfPreviewPage({  
662 - Key? key,  
663 - this.page,  
664 - this.pdfPreviewPageDecoration,  
665 - this.pageMargin,  
666 - }) : super(key: key);  
667 -  
668 - final PdfRaster? page;  
669 - final Decoration? pdfPreviewPageDecoration;  
670 - final EdgeInsets? pageMargin;  
671 -  
672 - @override  
673 - Widget build(BuildContext context) {  
674 - final im = PdfRasterImage(page!);  
675 - final scrollbarTrack = Theme.of(context)  
676 - .scrollbarTheme  
677 - .thickness  
678 - ?.resolve({MaterialState.hovered}) ??  
679 - 12;  
680 -  
681 - return Container(  
682 - margin: pageMargin ??  
683 - EdgeInsets.only(  
684 - left: 8 + scrollbarTrack,  
685 - top: 8,  
686 - right: 8 + scrollbarTrack,  
687 - bottom: 12,  
688 - ),  
689 - decoration: pdfPreviewPageDecoration ??  
690 - const BoxDecoration(  
691 - color: Colors.white,  
692 - boxShadow: <BoxShadow>[  
693 - BoxShadow(  
694 - offset: Offset(0, 3),  
695 - blurRadius: 5,  
696 - color: Color(0xFF000000),  
697 - ),  
698 - ],  
699 - ),  
700 - child: AspectRatio(  
701 - aspectRatio: page!.width / page!.height,  
702 - child: Image(  
703 - image: im,  
704 - fit: BoxFit.cover,  
705 - ),  
706 - ),  
707 - );  
708 - }  
709 -}  
710 -  
711 -/// Action callback  
712 -typedef OnPdfPreviewActionPressed = void Function(  
713 - BuildContext context,  
714 - LayoutCallback build,  
715 - PdfPageFormat pageFormat,  
716 -);  
717 -  
718 -/// Action to add the the [PdfPreview] widget  
719 -class PdfPreviewAction {  
720 - /// Represents an icon to add to [PdfPreview]  
721 - const PdfPreviewAction({  
722 - required this.icon,  
723 - required this.onPressed,  
724 - });  
725 -  
726 - /// The icon to display  
727 - final Icon icon;  
728 -  
729 - /// The callback called when the user tap on the icon  
730 - final OnPdfPreviewActionPressed? onPressed;  
731 -}  
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'package:flutter/material.dart';
  18 +import 'package:pdf/pdf.dart';
  19 +
  20 +import '../callback.dart';
  21 +
  22 +/// Base Action callback
  23 +typedef OnPdfPreviewActionPressed = void Function(
  24 + BuildContext context,
  25 + LayoutCallback build,
  26 + PdfPageFormat pageFormat,
  27 +);
  28 +
  29 +/// Action to add the the [PdfPreview] widget
  30 +class PdfPreviewAction {
  31 + /// Represents an icon to add to [PdfPreview]
  32 + const PdfPreviewAction({
  33 + required this.icon,
  34 + required this.onPressed,
  35 + });
  36 +
  37 + /// The icon to display
  38 + final Icon icon;
  39 +
  40 + /// The callback called when the user tap on the icon
  41 + final OnPdfPreviewActionPressed? onPressed;
  42 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'package:flutter/foundation.dart';
  18 +import 'package:flutter/material.dart';
  19 +
  20 +import '../raster.dart';
  21 +
  22 +/// Represents one PDF page
  23 +class PdfPreviewPage extends StatelessWidget {
  24 + /// Create a PDF page widget
  25 + const PdfPreviewPage({
  26 + Key? key,
  27 + this.page,
  28 + this.pdfPreviewPageDecoration,
  29 + this.pageMargin,
  30 + }) : super(key: key);
  31 +
  32 + /// Image representing the content of the page
  33 + final PdfRaster? page;
  34 +
  35 + /// Decoration around the page
  36 + final Decoration? pdfPreviewPageDecoration;
  37 +
  38 + /// Margin
  39 + final EdgeInsets? pageMargin;
  40 +
  41 + @override
  42 + Widget build(BuildContext context) {
  43 + final im = PdfRasterImage(page!);
  44 + final scrollbarTrack = Theme.of(context)
  45 + .scrollbarTheme
  46 + .thickness
  47 + ?.resolve({MaterialState.hovered}) ??
  48 + 12;
  49 +
  50 + return Container(
  51 + margin: pageMargin ??
  52 + EdgeInsets.only(
  53 + left: 8 + scrollbarTrack,
  54 + top: 8,
  55 + right: 8 + scrollbarTrack,
  56 + bottom: 12,
  57 + ),
  58 + decoration: pdfPreviewPageDecoration ??
  59 + const BoxDecoration(
  60 + color: Colors.white,
  61 + boxShadow: <BoxShadow>[
  62 + BoxShadow(
  63 + offset: Offset(0, 3),
  64 + blurRadius: 5,
  65 + color: Color(0xFF000000),
  66 + ),
  67 + ],
  68 + ),
  69 + child: AspectRatio(
  70 + aspectRatio: page!.width / page!.height,
  71 + child: Image(
  72 + image: im,
  73 + fit: BoxFit.cover,
  74 + ),
  75 + ),
  76 + );
  77 + }
  78 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'dart:async';
  18 +import 'dart:math';
  19 +import 'dart:typed_data';
  20 +
  21 +import 'package:flutter/foundation.dart';
  22 +import 'package:flutter/material.dart';
  23 +import 'package:pdf/pdf.dart';
  24 +
  25 +import '../printing.dart';
  26 +import '../printing_info.dart';
  27 +import '../raster.dart';
  28 +import 'pdf_preview.dart';
  29 +import 'pdf_preview_page.dart';
  30 +
  31 +/// Raster PDF documents
  32 +mixin PdfPreviewRaster on State<PdfPreview> {
  33 + static const _updateTime = Duration(milliseconds: 300);
  34 +
  35 + /// Configured page format
  36 + PdfPageFormat get pageFormat;
  37 +
  38 + /// Is the print horizontal
  39 + bool? horizontal;
  40 +
  41 + /// Resulting pages
  42 + final List<PdfPreviewPage> pages = <PdfPreviewPage>[];
  43 +
  44 + /// Printing subsystem information
  45 + PrintingInfo? info;
  46 +
  47 + /// Error message
  48 + Object? error;
  49 +
  50 + /// Dots per inch
  51 + double dpi = 10;
  52 +
  53 + var _rastering = false;
  54 +
  55 + Timer? _previewUpdate;
  56 +
  57 + @override
  58 + void dispose() {
  59 + _previewUpdate?.cancel();
  60 + super.dispose();
  61 + }
  62 +
  63 + /// Computed page format
  64 + PdfPageFormat get computedPageFormat => horizontal != null
  65 + ? (horizontal! ? pageFormat.landscape : pageFormat.portrait)
  66 + : pageFormat;
  67 +
  68 + /// Rasterize the document
  69 + void raster() {
  70 + _previewUpdate?.cancel();
  71 + _previewUpdate = Timer(_updateTime, () {
  72 + final mq = MediaQuery.of(context);
  73 + dpi = (min(mq.size.width - 16, widget.maxPageWidth ?? double.infinity)) *
  74 + mq.devicePixelRatio /
  75 + computedPageFormat.width *
  76 + 72;
  77 +
  78 + _raster();
  79 + });
  80 + }
  81 +
  82 + Future<void> _raster() async {
  83 + if (_rastering) {
  84 + return;
  85 + }
  86 + _rastering = true;
  87 +
  88 + Uint8List _doc;
  89 +
  90 + final _info = info;
  91 + if (_info != null && !_info.canRaster) {
  92 + assert(() {
  93 + if (kIsWeb) {
  94 + FlutterError.reportError(FlutterErrorDetails(
  95 + exception: Exception(
  96 + 'Unable to find the `pdf.js` library.\nPlease follow the installation instructions at https://github.com/DavBfr/dart_pdf/tree/master/printing#installing'),
  97 + library: 'printing',
  98 + context: ErrorDescription('while rendering a PDF'),
  99 + ));
  100 + }
  101 +
  102 + return true;
  103 + }());
  104 +
  105 + _rastering = false;
  106 + return;
  107 + }
  108 +
  109 + try {
  110 + _doc = await widget.build(computedPageFormat);
  111 + } catch (exception, stack) {
  112 + InformationCollector? collector;
  113 +
  114 + assert(() {
  115 + collector = () sync* {
  116 + yield StringProperty('PageFormat', computedPageFormat.toString());
  117 + };
  118 + return true;
  119 + }());
  120 +
  121 + FlutterError.reportError(FlutterErrorDetails(
  122 + exception: exception,
  123 + stack: stack,
  124 + library: 'printing',
  125 + context: ErrorDescription('while generating a PDF'),
  126 + informationCollector: collector,
  127 + ));
  128 + setState(() {
  129 + error = exception;
  130 + _rastering = false;
  131 + });
  132 + return;
  133 + }
  134 +
  135 + if (error != null) {
  136 + setState(() {
  137 + error = null;
  138 + });
  139 + }
  140 +
  141 + try {
  142 + var pageNum = 0;
  143 + await for (final PdfRaster page in Printing.raster(
  144 + _doc,
  145 + dpi: dpi,
  146 + pages: widget.pages,
  147 + )) {
  148 + if (!mounted) {
  149 + _rastering = false;
  150 + return;
  151 + }
  152 + setState(() {
  153 + if (pages.length <= pageNum) {
  154 + pages.add(PdfPreviewPage(
  155 + page: page,
  156 + pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration,
  157 + pageMargin: widget.previewPageMargin,
  158 + ));
  159 + } else {
  160 + pages[pageNum] = PdfPreviewPage(
  161 + page: page,
  162 + pdfPreviewPageDecoration: widget.pdfPreviewPageDecoration,
  163 + pageMargin: widget.previewPageMargin,
  164 + );
  165 + }
  166 + });
  167 +
  168 + pageNum++;
  169 + }
  170 +
  171 + pages.removeRange(pageNum, pages.length);
  172 + } catch (exception, stack) {
  173 + InformationCollector? collector;
  174 +
  175 + assert(() {
  176 + collector = () sync* {
  177 + yield StringProperty('PageFormat', computedPageFormat.toString());
  178 + };
  179 + return true;
  180 + }());
  181 +
  182 + FlutterError.reportError(FlutterErrorDetails(
  183 + exception: exception,
  184 + stack: stack,
  185 + library: 'printing',
  186 + context: ErrorDescription('while rastering a PDF'),
  187 + informationCollector: collector,
  188 + ));
  189 +
  190 + setState(() {
  191 + error = exception;
  192 + });
  193 + }
  194 +
  195 + _rastering = false;
  196 + }
  197 +}
@@ -7,7 +7,7 @@ description: > @@ -7,7 +7,7 @@ description: >
7 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing 7 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing
8 repository: https://github.com/DavBfr/dart_pdf 8 repository: https://github.com/DavBfr/dart_pdf
9 issue_tracker: https://github.com/DavBfr/dart_pdf/issues 9 issue_tracker: https://github.com/DavBfr/dart_pdf/issues
10 -version: 5.2.2 10 +version: 5.3.0
11 11
12 environment: 12 environment:
13 sdk: ">=2.12.0-0 <3.0.0" 13 sdk: ">=2.12.0-0 <3.0.0"
@@ -347,7 +347,7 @@ void PrintJob::rasterPdf(std::vector<uint8_t> data, @@ -347,7 +347,7 @@ void PrintJob::rasterPdf(std::vector<uint8_t> data,
347 347
348 FPDF_DestroyLibrary(); 348 FPDF_DestroyLibrary();
349 349
350 - printing->onPageRasterEnd(this, nullptr); 350 + printing->onPageRasterEnd(this, "");
351 } 351 }
352 352
353 std::map<std::string, bool> PrintJob::printingInfo() { 353 std::map<std::string, bool> PrintJob::printingInfo() {
@@ -44,7 +44,7 @@ void Printing::onPageRasterized(std::vector<uint8_t> data, @@ -44,7 +44,7 @@ void Printing::onPageRasterized(std::vector<uint8_t> data,
44 }))); 44 })));
45 } 45 }
46 46
47 -void Printing::onPageRasterEnd(PrintJob* job, const char* error) { 47 +void Printing::onPageRasterEnd(PrintJob* job, const std::string error) {
48 auto map = flutter::EncodableMap{ 48 auto map = flutter::EncodableMap{
49 {flutter::EncodableValue("job"), flutter::EncodableValue(job->id())}, 49 {flutter::EncodableValue("job"), flutter::EncodableValue(job->id())},
50 }; 50 };
@@ -40,7 +40,7 @@ class Printing { @@ -40,7 +40,7 @@ class Printing {
40 int height, 40 int height,
41 PrintJob* job); 41 PrintJob* job);
42 42
43 - void onPageRasterEnd(PrintJob* job, const char* error); 43 + void onPageRasterEnd(PrintJob* job, const std::string error);
44 44
45 void onLayout(PrintJob* job, 45 void onLayout(PrintJob* job,
46 double pageWidth, 46 double pageWidth,
@@ -50,7 +50,9 @@ class Printing { @@ -50,7 +50,9 @@ class Printing {
50 double marginRight, 50 double marginRight,
51 double marginBottom); 51 double marginBottom);
52 52
53 - void Printing::onCompleted(PrintJob* job, bool completed, std::string error); 53 + void Printing::onCompleted(PrintJob* job,
  54 + bool completed,
  55 + const std::string error);
54 }; 56 };
55 57
56 } // namespace nfet 58 } // namespace nfet