Showing
10 changed files
with
435 additions
and
266 deletions
| @@ -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; |
| 158 | + | ||
| 159 | + String get localPageFormat { | ||
| 160 | + final locale = WidgetsBinding.instance!.window.locale; | ||
| 161 | + // ignore: unnecessary_cast | ||
| 162 | + final cc = (locale as Locale?)?.countryCode ?? 'US'; | ||
| 137 | 163 | ||
| 138 | - late PdfPageFormat pageFormat; | 164 | + if (cc == 'US' || cc == 'CA' || cc == 'MX') { |
| 165 | + return 'Letter'; | ||
| 166 | + } | ||
| 167 | + return 'A4'; | ||
| 168 | + } | ||
| 139 | 169 | ||
| 140 | - bool? horizontal; | 170 | + @override |
| 171 | + PdfPageFormat get pageFormat { | ||
| 172 | + _pageFormat ??= widget.initialPageFormat == null | ||
| 173 | + ? widget.pageFormats[localPageFormat] | ||
| 174 | + : _pageFormat = widget.initialPageFormat!; | ||
| 141 | 175 | ||
| 142 | - PrintingInfo info = PrintingInfo.unavailable; | ||
| 143 | - bool infoLoaded = false; | 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 |
-
Please register or login to post a comment