David PHAM-VAN

Use plugin_platform_interface

@@ -34,11 +34,11 @@ printing/android/.gradle @@ -34,11 +34,11 @@ printing/android/.gradle
34 pedantic_analyis_options_*.yaml 34 pedantic_analyis_options_*.yaml
35 .dartfix 35 .dartfix
36 node_modules 36 node_modules
37 -pdf/lcov.info  
38 -pdf/coverage.json 37 +lcov.info
  38 +coverage.json
39 .coverage 39 .coverage
40 package-lock.json 40 package-lock.json
41 -printing/lcov.info 41 +lcov.info
42 42
43 test/readme.dart 43 test/readme.dart
44 test/android 44 test/android
@@ -77,18 +77,18 @@ test-pdf: $(FONTS) get-pdf .coverage @@ -77,18 +77,18 @@ test-pdf: $(FONTS) get-pdf .coverage
77 77
78 test-printing: $(FONTS) get-printing .coverage 78 test-printing: $(FONTS) get-printing .coverage
79 cd printing; flutter test --coverage --coverage-path lcov.info 79 cd printing; flutter test --coverage --coverage-path lcov.info
80 - cd printing/example; flutter test --coverage --coverage-path ../lcov.info 80 + cd printing/example; flutter test --coverage --coverage-path lcov.info
81 81
82 test-readme: $(FONTS) get-readme 82 test-readme: $(FONTS) get-readme
83 cd test; dart extract_readme.dart 83 cd test; dart extract_readme.dart
84 - cd test; dartanalyzer readme.dart 84 + cd test; dartanalyzer readme.dart
85 85
86 test-web: 86 test-web:
87 cd pdf/web_example; pub get 87 cd pdf/web_example; pub get
88 cd pdf/web_example; pub run webdev build 88 cd pdf/web_example; pub run webdev build
89 89
90 test: test-pdf test-printing node_modules test-web 90 test: test-pdf test-printing node_modules test-web
91 - cat pdf/lcov.info printing/lcov.info | node_modules/.bin/lcov-summary 91 + cat pdf/lcov.info printing/lcov.info printing/example/lcov.info | node_modules/.bin/lcov-summary
92 92
93 clean: 93 clean:
94 git clean -fdx -e .vscode -e ref 94 git clean -fdx -e .vscode -e ref
@@ -101,6 +101,14 @@ abstract class PdfFont extends PdfObject { @@ -101,6 +101,14 @@ abstract class PdfFont extends PdfObject {
101 pdfDocument, 'ZapfDingbats', 0.820, -0.143, _zapfDingbatsWidths); 101 pdfDocument, 'ZapfDingbats', 0.820, -0.143, _zapfDingbatsWidths);
102 } 102 }
103 103
  104 + static const String _cannotDecodeMessage =
  105 + '''---------------------------------------------
  106 +Cannot decode the string to Latin1.
  107 +This font does not support Unicode characters.
  108 +If you want to use strings other than Latin strings, use a TrueType (TTF) font instead.
  109 +See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management
  110 +---------------------------------------------''';
  111 +
104 /// The df type of the font, usually /Type1 112 /// The df type of the font, usually /Type1
105 final String subtype; 113 final String subtype;
106 114
@@ -141,13 +149,12 @@ abstract class PdfFont extends PdfObject { @@ -141,13 +149,12 @@ abstract class PdfFont extends PdfObject {
141 final Uint8List chars = latin1.encode(s); 149 final Uint8List chars = latin1.encode(s);
142 final Iterable<PdfFontMetrics> metrics = chars.map(glyphMetrics); 150 final Iterable<PdfFontMetrics> metrics = chars.map(glyphMetrics);
143 return PdfFontMetrics.append(metrics); 151 return PdfFontMetrics.append(metrics);
144 - } catch (e) {  
145 - assert(false, '''\n---------------------------------------------  
146 -Can not decode the string to Latin1.  
147 -This font does not support Unicode characters.  
148 -If you want to use strings other than Latin strings, use a TrueType (TTF) font instead.  
149 -See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management  
150 ----------------------------------------------'''); 152 + } catch (_) {
  153 + assert(() {
  154 + print(_cannotDecodeMessage);
  155 + return true;
  156 + }());
  157 +
151 rethrow; 158 rethrow;
152 } 159 }
153 } 160 }
@@ -169,13 +176,12 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management @@ -169,13 +176,12 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management
169 ..putByte(40) 176 ..putByte(40)
170 ..putTextBytes(latin1.encode(text)) 177 ..putTextBytes(latin1.encode(text))
171 ..putByte(41); 178 ..putByte(41);
172 - } catch (e) {  
173 - assert(false, '''\n---------------------------------------------  
174 -Can not decode the string to Latin1.  
175 -This font does not support Unicode characters.  
176 -If you want to use strings other than Latin strings, use a TrueType (TTF) font instead.  
177 -See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management  
178 ----------------------------------------------'''); 179 + } catch (_) {
  180 + assert(() {
  181 + print(_cannotDecodeMessage);
  182 + return true;
  183 + }());
  184 +
179 rethrow; 185 rethrow;
180 } 186 }
181 } 187 }
1 # Changelog 1 # Changelog
2 2
  3 +## 3.2.0
  4 +
  5 +- Update README
  6 +- Remove deprecated API
  7 +- Use plugin_platform_interface
  8 +- Fix inconsistent API
  9 +- Add Unit tests
  10 +
3 ## 3.1.0 11 ## 3.1.0
4 12
5 - Migrate to the new Android plugins APIs 13 - Migrate to the new Android plugins APIs
@@ -63,11 +63,17 @@ class MyAppState extends State<MyApp> { @@ -63,11 +63,17 @@ class MyAppState extends State<MyApp> {
63 63
64 Future<void> _printPdf() async { 64 Future<void> _printPdf() async {
65 print('Print ...'); 65 print('Print ...');
66 - final bool result = await Printing.layoutPdf(  
67 - onLayout: (PdfPageFormat format) async =>  
68 - (await generateDocument(format)).save());  
69 -  
70 - _showPrintedToast(result); 66 + try {
  67 + final bool result = await Printing.layoutPdf(
  68 + onLayout: (PdfPageFormat format) async =>
  69 + (await generateDocument(format)).save());
  70 + _showPrintedToast(result);
  71 + } catch (e) {
  72 + final ScaffoldState scaffold = Scaffold.of(shareWidget.currentContext);
  73 + scaffold.showSnackBar(SnackBar(
  74 + content: Text('Error: ${e.toString()}'),
  75 + ));
  76 + }
71 } 77 }
72 78
73 Future<void> _saveAsFile() async { 79 Future<void> _saveAsFile() async {
@@ -14,22 +14,9 @@ @@ -14,22 +14,9 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -library printing;  
18 -  
19 -import 'dart:async';  
20 -import 'dart:typed_data';  
21 -import 'dart:ui' as ui;  
22 -  
23 -import 'package:flutter/rendering.dart';  
24 -import 'package:flutter/services.dart';  
25 -import 'package:flutter/widgets.dart';  
26 -import 'package:pdf/pdf.dart';  
27 -import 'package:pdf/widgets.dart';  
28 -  
29 -part 'src/asset_utils.dart';  
30 -part 'src/print_job.dart';  
31 -part 'src/printer.dart';  
32 -part 'src/printing.dart';  
33 -part 'src/printing_info.dart';  
34 -part 'src/raster.dart';  
35 -part 'src/widgets.dart'; 17 +export 'src/asset_utils.dart';
  18 +export 'src/callback.dart';
  19 +export 'src/printer.dart';
  20 +export 'src/printing.dart';
  21 +export 'src/printing_info.dart';
  22 +export 'src/raster.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 -  
17 -library printing_web;  
18 -  
19 -import 'dart:async';  
20 -import 'dart:html' as html;  
21 -import 'dart:js' as js;  
22 -  
23 -import 'package:flutter/services.dart';  
24 -import 'package:flutter_web_plugins/flutter_web_plugins.dart';  
25 -  
26 -part 'wsrc/print_job.dart';  
27 -  
28 -class PrintingPlugin {  
29 - PrintingPlugin(this._channel);  
30 -  
31 - static void registerWith(Registrar registrar) {  
32 - final MethodChannel channel = MethodChannel(  
33 - 'net.nfet.printing',  
34 - const StandardMethodCodec(),  
35 - registrar.messenger,  
36 - );  
37 - final PrintingPlugin instance = PrintingPlugin(channel);  
38 - channel.setMethodCallHandler(instance.handleMethodCall);  
39 - }  
40 -  
41 - final MethodChannel _channel;  
42 -  
43 - Future<dynamic> handleMethodCall(MethodCall call) async {  
44 - switch (call.method) {  
45 - case 'printPdf':  
46 - final String name = call.arguments['name'];  
47 - final double width = call.arguments['width'];  
48 - final double height = call.arguments['height'];  
49 - final double marginLeft = call.arguments['marginLeft'];  
50 - final double marginTop = call.arguments['marginTop'];  
51 - final double marginRight = call.arguments['marginRight'];  
52 - final double marginBottom = call.arguments['marginBottom'];  
53 -  
54 - final _PrintJob printJob = _PrintJob(this, call.arguments['job']);  
55 - return printJob.printPdf(name, width, height, marginLeft, marginTop,  
56 - marginRight, marginBottom);  
57 - case 'sharePdf':  
58 - final List<int> data = call.arguments['doc'];  
59 - final double x = call.arguments['x'];  
60 - final double y = call.arguments['y'];  
61 - final double width = call.arguments['w'];  
62 - final double height = call.arguments['h'];  
63 - final String name = call.arguments['name'];  
64 - return _PrintJob.sharePdf(data, x, y, width, height, name);  
65 - case 'printingInfo':  
66 - return _PrintJob.printingInfo();  
67 - }  
68 - throw UnimplementedError('Method "${call.method}" not implemented');  
69 - }  
70 -  
71 - /// Request the Pdf document from flutter  
72 - Future<void> onLayout(  
73 - _PrintJob printJob,  
74 - double width,  
75 - double height,  
76 - double marginLeft,  
77 - double marginTop,  
78 - double marginRight,  
79 - double marginBottom) async {  
80 - final Map<String, dynamic> args = <String, dynamic>{  
81 - 'width': width,  
82 - 'height': height,  
83 - 'marginLeft': marginLeft,  
84 - 'marginTop': marginTop,  
85 - 'marginRight': marginRight,  
86 - 'marginBottom': marginBottom,  
87 - 'job': printJob.index,  
88 - };  
89 -  
90 - final dynamic result =  
91 - await _channel.invokeMethod<dynamic>('onLayout', args);  
92 -  
93 - if (result is List<int>) {  
94 - printJob.setDocument(result);  
95 - } else {  
96 - printJob.cancelJob();  
97 - }  
98 - }  
99 -  
100 - /// send completion status to flutter  
101 - Future<void> onCompleted(_PrintJob printJob, bool completed,  
102 - [String error = '']) async {  
103 - final Map<String, dynamic> data = <String, dynamic>{  
104 - 'completed': completed,  
105 - 'error': error,  
106 - 'job': printJob.index,  
107 - };  
108 -  
109 - await _channel.invokeMethod<dynamic>('onCompleted', data);  
110 - }  
111 -}  
@@ -14,7 +14,15 @@ @@ -14,7 +14,15 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -part of printing; 17 +import 'dart:async';
  18 +import 'dart:typed_data';
  19 +import 'dart:ui' as ui;
  20 +
  21 +import 'package:flutter/rendering.dart';
  22 +import 'package:flutter/services.dart';
  23 +import 'package:meta/meta.dart';
  24 +import 'package:pdf/pdf.dart';
  25 +import 'package:pdf/widgets.dart';
18 26
19 /// Loads an image from a Flutter [ui.Image] 27 /// Loads an image from a Flutter [ui.Image]
20 /// into a [PdfImage] instance 28 /// into a [PdfImage] instance
  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 +
  19 +import 'package:pdf/pdf.dart';
  20 +
  21 +/// Callback used to generate the Pdf document dynamically when the user
  22 +/// changes the page settings: size and margins
  23 +typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format);
  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 +
  19 +import 'package:flutter/rendering.dart' show Rect;
  20 +import 'package:pdf/pdf.dart';
  21 +import 'package:plugin_platform_interface/plugin_platform_interface.dart';
  22 +
  23 +import 'callback.dart';
  24 +import 'method_channel.dart';
  25 +import 'printer.dart';
  26 +import 'printing_info.dart';
  27 +import 'raster.dart';
  28 +
  29 +/// The interface that implementations of printing must implement.
  30 +abstract class PrintingPlatform extends PlatformInterface {
  31 + /// Constructs a UrlLauncherPlatform.
  32 + PrintingPlatform() : super(token: _token);
  33 +
  34 + static final Object _token = Object();
  35 +
  36 + static PrintingPlatform _instance = MethodChannelPrinting();
  37 +
  38 + /// The default instance of [PrintingPlatform] to use.
  39 + ///
  40 + /// Defaults to [MethodChannelPrinting].
  41 + static PrintingPlatform get instance => _instance;
  42 +
  43 + /// Platform-specific plugins should set this with their own platform-specific
  44 + /// class that extends [PrintingPlatform] when they register themselves.
  45 + static set instance(PrintingPlatform instance) {
  46 + PlatformInterface.verifyToken(instance, _token);
  47 + _instance = instance;
  48 + }
  49 +
  50 + /// Returns a [PrintingInfo] object representing the capabilities
  51 + /// supported for the current platform
  52 + Future<PrintingInfo> info();
  53 +
  54 + /// Prints a Pdf document to a local printer using the platform UI
  55 + /// the Pdf document is re-built in a [LayoutCallback] each time the
  56 + /// user changes a setting like the page format or orientation.
  57 + ///
  58 + /// returns a future with a `bool` set to true if the document is printed
  59 + /// and false if it is canceled.
  60 + /// throws an exception in case of error
  61 + Future<bool> layoutPdf(
  62 + LayoutCallback onLayout,
  63 + String name,
  64 + PdfPageFormat format,
  65 + );
  66 +
  67 + /// Opens the native printer picker interface, and returns the URL of the selected printer.
  68 + Future<Printer> pickPrinter(Rect bounds);
  69 +
  70 + /// Prints a Pdf document to a specific local printer with no UI
  71 + ///
  72 + /// returns a future with a `bool` set to true if the document is printed
  73 + /// and false if it is canceled.
  74 + /// throws an exception in case of error
  75 + Future<bool> directPrintPdf(
  76 + Printer printer,
  77 + LayoutCallback onLayout,
  78 + String name,
  79 + PdfPageFormat format,
  80 + );
  81 +
  82 + /// Displays a platform popup to share the Pdf document to another application
  83 + Future<bool> sharePdf(
  84 + List<int> bytes,
  85 + String filename,
  86 + Rect bounds,
  87 + );
  88 +
  89 + /// Convert an html document to a pdf data
  90 + Future<List<int>> convertHtml(
  91 + String html,
  92 + String baseUrl,
  93 + PdfPageFormat format,
  94 + );
  95 +
  96 + /// Convert a Pdf document to bitmap images
  97 + Stream<PdfRaster> raster(
  98 + List<int> document,
  99 + List<int> pages,
  100 + double dpi,
  101 + );
  102 +}
  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:typed_data';
  19 +
  20 +import 'package:flutter/rendering.dart' show Rect;
  21 +import 'package:flutter/services.dart';
  22 +import 'package:pdf/pdf.dart';
  23 +
  24 +import 'callback.dart';
  25 +import 'interface.dart';
  26 +import 'print_job.dart';
  27 +import 'printer.dart';
  28 +import 'printing_info.dart';
  29 +import 'raster.dart';
  30 +
  31 +const MethodChannel _channel = MethodChannel('net.nfet.printing');
  32 +
  33 +/// An implementation of [PrintingPlatform] that uses method channels.
  34 +class MethodChannelPrinting extends PrintingPlatform {
  35 + MethodChannelPrinting() : super() {
  36 + _channel.setMethodCallHandler(_handleMethod);
  37 + }
  38 +
  39 + static final Map<int, PrintJob> _printJobs = <int, PrintJob>{};
  40 + static int _jobIndex = 0;
  41 +
  42 + /// Callbacks from platform plugins
  43 + static Future<dynamic> _handleMethod(MethodCall call) async {
  44 + switch (call.method) {
  45 + case 'onLayout':
  46 + final PrintJob job = _printJobs[call.arguments['job']];
  47 + try {
  48 + final PdfPageFormat format = PdfPageFormat(
  49 + call.arguments['width'],
  50 + call.arguments['height'],
  51 + marginLeft: call.arguments['marginLeft'],
  52 + marginTop: call.arguments['marginTop'],
  53 + marginRight: call.arguments['marginRight'],
  54 + marginBottom: call.arguments['marginBottom'],
  55 + );
  56 +
  57 + final List<int> bytes = await job.onLayout(format);
  58 +
  59 + if (bytes == null) {
  60 + throw 'onLayout returned null';
  61 + }
  62 +
  63 + return Uint8List.fromList(bytes);
  64 + } catch (e) {
  65 + return e.toString();
  66 + }
  67 + break;
  68 + case 'onCompleted':
  69 + final bool completed = call.arguments['completed'];
  70 + final String error = call.arguments['error'];
  71 + final PrintJob job = _printJobs[call.arguments['job']];
  72 + if (completed == false && error != null) {
  73 + job.onCompleted.completeError(error);
  74 + } else {
  75 + job.onCompleted.complete(completed);
  76 + }
  77 + break;
  78 + case 'onHtmlRendered':
  79 + final PrintJob job = _printJobs[call.arguments['job']];
  80 + job.onHtmlRendered.complete(call.arguments['doc']);
  81 + break;
  82 + case 'onHtmlError':
  83 + final PrintJob job = _printJobs[call.arguments['job']];
  84 + job.onHtmlRendered.completeError(call.arguments['error']);
  85 + break;
  86 + case 'onPageRasterized':
  87 + final PrintJob job = _printJobs[call.arguments['job']];
  88 + final PdfRaster raster = PdfRaster(
  89 + call.arguments['width'],
  90 + call.arguments['height'],
  91 + call.arguments['image'],
  92 + );
  93 + job.onPageRasterized.add(raster);
  94 + break;
  95 + case 'onPageRasterEnd':
  96 + final PrintJob job = _printJobs[call.arguments['job']];
  97 + job.onPageRasterized.close();
  98 + _printJobs.remove(job.index);
  99 + break;
  100 + }
  101 + }
  102 +
  103 + static PrintJob _newPrintJob(PrintJob job) {
  104 + job.index = _jobIndex++;
  105 + _printJobs[job.index] = job;
  106 + return job;
  107 + }
  108 +
  109 + @override
  110 + Future<PrintingInfo> info() async {
  111 + _channel.setMethodCallHandler(_handleMethod);
  112 + Map<dynamic, dynamic> result;
  113 +
  114 + try {
  115 + result = await _channel.invokeMethod(
  116 + 'printingInfo',
  117 + <String, dynamic>{},
  118 + );
  119 + } catch (e) {
  120 + print('Error getting printing info: $e');
  121 + return PrintingInfo.unavailable;
  122 + }
  123 +
  124 + return PrintingInfo.fromMap(result);
  125 + }
  126 +
  127 + @override
  128 + Future<bool> layoutPdf(
  129 + LayoutCallback onLayout,
  130 + String name,
  131 + PdfPageFormat format,
  132 + ) async {
  133 + final PrintJob job = _newPrintJob(PrintJob(
  134 + onCompleted: Completer<bool>(),
  135 + onLayout: onLayout,
  136 + ));
  137 +
  138 + final Map<String, dynamic> params = <String, dynamic>{
  139 + 'name': name,
  140 + 'job': job.index,
  141 + 'width': format.width,
  142 + 'height': format.height,
  143 + 'marginLeft': format.marginLeft,
  144 + 'marginTop': format.marginTop,
  145 + 'marginRight': format.marginRight,
  146 + 'marginBottom': format.marginBottom,
  147 + };
  148 +
  149 + await _channel.invokeMethod<int>('printPdf', params);
  150 + try {
  151 + return await job.onCompleted.future;
  152 + } finally {
  153 + _printJobs.remove(job.index);
  154 + }
  155 + }
  156 +
  157 + @override
  158 + Future<Printer> pickPrinter(Rect bounds) async {
  159 + final Map<String, dynamic> params = <String, dynamic>{
  160 + 'x': bounds.left,
  161 + 'y': bounds.top,
  162 + 'w': bounds.width,
  163 + 'h': bounds.height,
  164 + };
  165 + final Map<dynamic, dynamic> printer = await _channel
  166 + .invokeMethod<Map<dynamic, dynamic>>('pickPrinter', params);
  167 + if (printer == null) {
  168 + return null;
  169 + }
  170 + return Printer(
  171 + url: printer['url'],
  172 + name: printer['name'],
  173 + model: printer['model'],
  174 + location: printer['location'],
  175 + );
  176 + }
  177 +
  178 + @override
  179 + Future<bool> directPrintPdf(
  180 + Printer printer,
  181 + LayoutCallback onLayout,
  182 + String name,
  183 + PdfPageFormat format,
  184 + ) async {
  185 + final PrintJob job = _newPrintJob(PrintJob(
  186 + onCompleted: Completer<bool>(),
  187 + ));
  188 +
  189 + final List<int> bytes = await onLayout(format);
  190 + if (bytes == null) {
  191 + return false;
  192 + }
  193 +
  194 + final Map<String, dynamic> params = <String, dynamic>{
  195 + 'name': name,
  196 + 'printer': printer.url,
  197 + 'doc': Uint8List.fromList(bytes),
  198 + 'job': job.index,
  199 + };
  200 + await _channel.invokeMethod<int>('directPrintPdf', params);
  201 + final bool result = await job.onCompleted.future;
  202 + _printJobs.remove(job.index);
  203 + return result;
  204 + }
  205 +
  206 + @override
  207 + Future<bool> sharePdf(
  208 + List<int> bytes,
  209 + String filename,
  210 + Rect bounds,
  211 + ) async {
  212 + final Map<String, dynamic> params = <String, dynamic>{
  213 + 'doc': Uint8List.fromList(bytes),
  214 + 'name': filename,
  215 + 'x': bounds.left,
  216 + 'y': bounds.top,
  217 + 'w': bounds.width,
  218 + 'h': bounds.height,
  219 + };
  220 + return await _channel.invokeMethod('sharePdf', params);
  221 + }
  222 +
  223 + @override
  224 + Future<List<int>> convertHtml(
  225 + String html, String baseUrl, PdfPageFormat format) async {
  226 + final PrintJob job = _newPrintJob(PrintJob(
  227 + onHtmlRendered: Completer<List<int>>(),
  228 + ));
  229 +
  230 + final Map<String, dynamic> params = <String, dynamic>{
  231 + 'html': html,
  232 + 'baseUrl': baseUrl,
  233 + 'width': format.width,
  234 + 'height': format.height,
  235 + 'marginLeft': format.marginLeft,
  236 + 'marginTop': format.marginTop,
  237 + 'marginRight': format.marginRight,
  238 + 'marginBottom': format.marginBottom,
  239 + 'job': job.index,
  240 + };
  241 +
  242 + await _channel.invokeMethod<void>('convertHtml', params);
  243 + final List<int> result = await job.onHtmlRendered.future;
  244 + _printJobs.remove(job.index);
  245 + return result;
  246 + }
  247 +
  248 + @override
  249 + Stream<PdfRaster> raster(
  250 + List<int> document,
  251 + List<int> pages,
  252 + double dpi,
  253 + ) {
  254 + final PrintJob job = _newPrintJob(PrintJob(
  255 + onPageRasterized: StreamController<PdfRaster>(),
  256 + ));
  257 +
  258 + final Map<String, dynamic> params = <String, dynamic>{
  259 + 'doc': Uint8List.fromList(document),
  260 + 'pages': pages,
  261 + 'scale': dpi / PdfPageFormat.inch,
  262 + 'job': job.index,
  263 + };
  264 +
  265 + _channel.invokeMethod<void>('rasterPdf', params);
  266 + return job.onPageRasterized.stream;
  267 + }
  268 +}
@@ -14,10 +14,13 @@ @@ -14,10 +14,13 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -part of printing; 17 +import 'dart:async';
18 18
19 -class _PrintJob {  
20 - _PrintJob({ 19 +import 'callback.dart';
  20 +import 'raster.dart';
  21 +
  22 +class PrintJob {
  23 + PrintJob({
21 this.onLayout, 24 this.onLayout,
22 this.onHtmlRendered, 25 this.onHtmlRendered,
23 this.onCompleted, 26 this.onCompleted,
@@ -14,10 +14,12 @@ @@ -14,10 +14,12 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -part of printing; 17 +import 'package:meta/meta.dart';
18 18
  19 +/// Information about a printer
19 @immutable 20 @immutable
20 class Printer { 21 class Printer {
  22 + /// Create a printer information
21 const Printer({ 23 const Printer({
22 @required this.url, 24 @required this.url,
23 this.name, 25 this.name,
@@ -25,9 +27,16 @@ class Printer { @@ -25,9 +27,16 @@ class Printer {
25 this.location, 27 this.location,
26 }) : assert(url != null); 28 }) : assert(url != null);
27 29
  30 + /// The platform specific printer identification
28 final String url; 31 final String url;
  32 +
  33 + /// The display name of the printer
29 final String name; 34 final String name;
  35 +
  36 + /// The printer model
30 final String model; 37 final String model;
  38 +
  39 + /// The physical location of the printer
31 final String location; 40 final String location;
32 41
33 @override 42 @override
@@ -14,83 +14,19 @@ @@ -14,83 +14,19 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -part of printing; 17 +import 'dart:async';
18 18
19 -typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format); 19 +import 'package:flutter/rendering.dart' show Rect, Offset;
  20 +import 'package:meta/meta.dart';
  21 +import 'package:pdf/pdf.dart';
20 22
21 -mixin Printing {  
22 - static const MethodChannel _channel = MethodChannel('net.nfet.printing');  
23 - static final Map<int, _PrintJob> _printJobs = <int, _PrintJob>{};  
24 - static int _jobIndex = 0;  
25 -  
26 - /// Callbacks from platform plugins  
27 - static Future<dynamic> _handleMethod(MethodCall call) async {  
28 - switch (call.method) {  
29 - case 'onLayout':  
30 - final _PrintJob job = _printJobs[call.arguments['job']];  
31 - try {  
32 - final PdfPageFormat format = PdfPageFormat(  
33 - call.arguments['width'],  
34 - call.arguments['height'],  
35 - marginLeft: call.arguments['marginLeft'],  
36 - marginTop: call.arguments['marginTop'],  
37 - marginRight: call.arguments['marginRight'],  
38 - marginBottom: call.arguments['marginBottom'],  
39 - );  
40 -  
41 - final List<int> bytes = await job.onLayout(format);  
42 -  
43 - if (bytes == null) {  
44 - return false;  
45 - }  
46 -  
47 - return Uint8List.fromList(bytes);  
48 - } catch (e) {  
49 - print('Unable to print: $e');  
50 - return false;  
51 - }  
52 - break;  
53 - case 'onCompleted':  
54 - final bool completed = call.arguments['completed'];  
55 - final String error = call.arguments['error'];  
56 - final _PrintJob job = _printJobs[call.arguments['job']];  
57 - if (completed == false && error != null) {  
58 - job.onCompleted.completeError(error);  
59 - } else {  
60 - job.onCompleted.complete(completed);  
61 - }  
62 - break;  
63 - case 'onHtmlRendered':  
64 - final _PrintJob job = _printJobs[call.arguments['job']];  
65 - job.onHtmlRendered.complete(call.arguments['doc']);  
66 - break;  
67 - case 'onHtmlError':  
68 - final _PrintJob job = _printJobs[call.arguments['job']];  
69 - job.onHtmlRendered.completeError(call.arguments['error']);  
70 - break;  
71 - case 'onPageRasterized':  
72 - final _PrintJob job = _printJobs[call.arguments['job']];  
73 - final PdfRaster raster = PdfRaster._(  
74 - call.arguments['width'],  
75 - call.arguments['height'],  
76 - call.arguments['image'],  
77 - );  
78 - job.onPageRasterized.add(raster);  
79 - break;  
80 - case 'onPageRasterEnd':  
81 - final _PrintJob job = _printJobs[call.arguments['job']];  
82 - job.onPageRasterized.close();  
83 - _printJobs.remove(job.index);  
84 - break;  
85 - }  
86 - }  
87 -  
88 - static _PrintJob _newPrintJob(_PrintJob job) {  
89 - job.index = _jobIndex++;  
90 - _printJobs[job.index] = job;  
91 - return job;  
92 - } 23 +import 'callback.dart';
  24 +import 'interface.dart';
  25 +import 'printer.dart';
  26 +import 'printing_info.dart';
  27 +import 'raster.dart';
93 28
  29 +mixin Printing {
94 /// Prints a Pdf document to a local printer using the platform UI 30 /// Prints a Pdf document to a local printer using the platform UI
95 /// the Pdf document is re-built in a [LayoutCallback] each time the 31 /// the Pdf document is re-built in a [LayoutCallback] each time the
96 /// user changes a setting like the page format or orientation. 32 /// user changes a setting like the page format or orientation.
@@ -102,61 +38,19 @@ mixin Printing { @@ -102,61 +38,19 @@ mixin Printing {
102 @required LayoutCallback onLayout, 38 @required LayoutCallback onLayout,
103 String name = 'Document', 39 String name = 'Document',
104 PdfPageFormat format = PdfPageFormat.standard, 40 PdfPageFormat format = PdfPageFormat.standard,
105 - }) async {  
106 - _channel.setMethodCallHandler(_handleMethod);  
107 -  
108 - final _PrintJob job = _newPrintJob(_PrintJob(  
109 - onCompleted: Completer<bool>(),  
110 - onLayout: onLayout,  
111 - ));  
112 -  
113 - final Map<String, dynamic> params = <String, dynamic>{  
114 - 'name': name,  
115 - 'job': job.index,  
116 - 'width': format.width,  
117 - 'height': format.height,  
118 - 'marginLeft': format.marginLeft,  
119 - 'marginTop': format.marginTop,  
120 - 'marginRight': format.marginRight,  
121 - 'marginBottom': format.marginBottom,  
122 - }; 41 + }) {
  42 + assert(onLayout != null);
  43 + assert(name != null);
  44 + assert(format != null);
123 45
124 - await _channel.invokeMethod<int>('printPdf', params);  
125 - try {  
126 - return await job.onCompleted.future;  
127 - } finally {  
128 - _printJobs.remove(job.index);  
129 - }  
130 - }  
131 -  
132 - static Future<Map<dynamic, dynamic>> printingInfo() async {  
133 - return await _channel.invokeMethod<Map<dynamic, dynamic>>(  
134 - 'printingInfo',  
135 - <String, dynamic>{},  
136 - ); 46 + return PrintingPlatform.instance.layoutPdf(onLayout, name, format);
137 } 47 }
138 48
139 /// Opens the native printer picker interface, and returns the URL of the selected printer. 49 /// Opens the native printer picker interface, and returns the URL of the selected printer.
140 - static Future<Printer> pickPrinter({Rect bounds}) async {  
141 - _channel.setMethodCallHandler(_handleMethod); 50 + static Future<Printer> pickPrinter({Rect bounds}) {
142 bounds ??= Rect.fromCircle(center: Offset.zero, radius: 10); 51 bounds ??= Rect.fromCircle(center: Offset.zero, radius: 10);
143 - final Map<String, dynamic> params = <String, dynamic>{  
144 - 'x': bounds.left,  
145 - 'y': bounds.top,  
146 - 'w': bounds.width,  
147 - 'h': bounds.height,  
148 - };  
149 - final Map<dynamic, dynamic> printer = await _channel  
150 - .invokeMethod<Map<dynamic, dynamic>>('pickPrinter', params);  
151 - if (printer == null) {  
152 - return null;  
153 - }  
154 - return Printer(  
155 - url: printer['url'],  
156 - name: printer['name'],  
157 - model: printer['model'],  
158 - location: printer['location'],  
159 - ); 52 +
  53 + return PrintingPlatform.instance.pickPrinter(bounds);
160 } 54 }
161 55
162 /// Prints a Pdf document to a specific local printer with no UI 56 /// Prints a Pdf document to a specific local printer with no UI
@@ -164,60 +58,35 @@ mixin Printing { @@ -164,60 +58,35 @@ mixin Printing {
164 /// returns a future with a `bool` set to true if the document is printed 58 /// returns a future with a `bool` set to true if the document is printed
165 /// and false if it is canceled. 59 /// and false if it is canceled.
166 /// throws an exception in case of error 60 /// throws an exception in case of error
167 - static Future<bool> directPrintPdf({ 61 + static FutureOr<bool> directPrintPdf({
168 @required Printer printer, 62 @required Printer printer,
169 @required LayoutCallback onLayout, 63 @required LayoutCallback onLayout,
170 String name = 'Document', 64 String name = 'Document',
171 PdfPageFormat format = PdfPageFormat.standard, 65 PdfPageFormat format = PdfPageFormat.standard,
172 - }) async { 66 + }) {
173 if (printer == null) { 67 if (printer == null) {
174 return false; 68 return false;
175 } 69 }
176 70
177 - _channel.setMethodCallHandler(_handleMethod);  
178 -  
179 - final _PrintJob job = _newPrintJob(_PrintJob(  
180 - onCompleted: Completer<bool>(),  
181 - ));  
182 -  
183 - final List<int> bytes = await onLayout(format);  
184 - if (bytes == null) {  
185 - return false;  
186 - }  
187 -  
188 - final Map<String, dynamic> params = <String, dynamic>{  
189 - 'name': name,  
190 - 'printer': printer.url,  
191 - 'doc': Uint8List.fromList(bytes),  
192 - 'job': job.index,  
193 - };  
194 - await _channel.invokeMethod<int>('directPrintPdf', params);  
195 - final bool result = await job.onCompleted.future;  
196 - _printJobs.remove(job.index);  
197 - return result;  
198 - }  
199 -  
200 - /// Prints a [PdfDocument] or a pdf stream to a local printer using the platform UI  
201 - @Deprecated('use Printing.layoutPdf(onLayout: (_) => document.save());')  
202 - static Future<void> printPdf({  
203 - @Deprecated('use bytes with document.save()') PdfDocument document,  
204 - List<int> bytes,  
205 - }) async {  
206 - assert(document != null || bytes != null);  
207 - assert(!(document == null && bytes == null)); 71 + assert(onLayout != null);
  72 + assert(name != null);
  73 + assert(format != null);
208 74
209 - layoutPdf(  
210 - onLayout: (PdfPageFormat format) =>  
211 - document != null ? document.save() : bytes); 75 + return PrintingPlatform.instance.directPrintPdf(
  76 + printer,
  77 + onLayout,
  78 + name,
  79 + format,
  80 + );
212 } 81 }
213 82
214 /// Displays a platform popup to share the Pdf document to another application 83 /// Displays a platform popup to share the Pdf document to another application
215 - static Future<void> sharePdf({ 84 + static Future<bool> sharePdf({
216 @Deprecated('use bytes with document.save()') PdfDocument document, 85 @Deprecated('use bytes with document.save()') PdfDocument document,
217 List<int> bytes, 86 List<int> bytes,
218 String filename = 'document.pdf', 87 String filename = 'document.pdf',
219 Rect bounds, 88 Rect bounds,
220 - }) async { 89 + }) {
221 assert(document != null || bytes != null); 90 assert(document != null || bytes != null);
222 assert(!(document == null && bytes == null)); 91 assert(!(document == null && bytes == null));
223 assert(filename != null); 92 assert(filename != null);
@@ -228,61 +97,40 @@ mixin Printing { @@ -228,61 +97,40 @@ mixin Printing {
228 97
229 bounds ??= Rect.fromCircle(center: Offset.zero, radius: 10); 98 bounds ??= Rect.fromCircle(center: Offset.zero, radius: 10);
230 99
231 - final Map<String, dynamic> params = <String, dynamic>{  
232 - 'doc': Uint8List.fromList(bytes),  
233 - 'name': filename,  
234 - 'x': bounds.left,  
235 - 'y': bounds.top,  
236 - 'w': bounds.width,  
237 - 'h': bounds.height,  
238 - };  
239 - return await _channel.invokeMethod('sharePdf', params); 100 + return PrintingPlatform.instance.sharePdf(
  101 + bytes,
  102 + filename,
  103 + bounds,
  104 + );
240 } 105 }
241 106
242 /// Convert an html document to a pdf data 107 /// Convert an html document to a pdf data
243 - static Future<List<int>> convertHtml(  
244 - {@required String html,  
245 - String baseUrl,  
246 - PdfPageFormat format = PdfPageFormat.a4}) async {  
247 - _channel.setMethodCallHandler(_handleMethod);  
248 -  
249 - final _PrintJob job = _newPrintJob(_PrintJob(  
250 - onHtmlRendered: Completer<List<int>>(),  
251 - ));  
252 -  
253 - final Map<String, dynamic> params = <String, dynamic>{  
254 - 'html': html,  
255 - 'baseUrl': baseUrl,  
256 - 'width': format.width,  
257 - 'height': format.height,  
258 - 'marginLeft': format.marginLeft,  
259 - 'marginTop': format.marginTop,  
260 - 'marginRight': format.marginRight,  
261 - 'marginBottom': format.marginBottom,  
262 - 'job': job.index,  
263 - }; 108 + static Future<List<int>> convertHtml({
  109 + @required String html,
  110 + String baseUrl,
  111 + PdfPageFormat format = PdfPageFormat.standard,
  112 + }) {
  113 + assert(html != null);
  114 + assert(format != null);
264 115
265 - await _channel.invokeMethod<void>('convertHtml', params);  
266 - final List<int> result = await job.onHtmlRendered.future;  
267 - _printJobs.remove(job.index);  
268 - return result; 116 + return PrintingPlatform.instance.convertHtml(
  117 + html,
  118 + baseUrl,
  119 + format,
  120 + );
269 } 121 }
270 122
271 - static Future<PrintingInfo> info() async {  
272 - _channel.setMethodCallHandler(_handleMethod);  
273 - Map<dynamic, dynamic> result;  
274 -  
275 - try {  
276 - result = await _channel.invokeMethod(  
277 - 'printingInfo',  
278 - <String, dynamic>{},  
279 - );  
280 - } catch (e) {  
281 - print('Error getting printing info: $e');  
282 - return PrintingInfo.unavailable;  
283 - } 123 + /// Returns a [PrintingInfo] object representing the capabilities
  124 + /// supported for the current platform
  125 + static Future<PrintingInfo> info() {
  126 + return PrintingPlatform.instance.info();
  127 + }
284 128
285 - return PrintingInfo.fromMap(result); 129 + /// Returns a [PrintingInfo] object representing the capabilities
  130 + /// supported for the current platform as a map
  131 + @Deprecated('Use Printing.info()')
  132 + static Future<Map<dynamic, dynamic>> printingInfo() async {
  133 + return (await info()).asMap();
286 } 134 }
287 135
288 static Stream<PdfRaster> raster( 136 static Stream<PdfRaster> raster(
@@ -290,20 +138,25 @@ mixin Printing { @@ -290,20 +138,25 @@ mixin Printing {
290 List<int> pages, 138 List<int> pages,
291 double dpi = PdfPageFormat.inch, 139 double dpi = PdfPageFormat.inch,
292 }) { 140 }) {
293 - _channel.setMethodCallHandler(_handleMethod); 141 + assert(document != null);
  142 + assert(dpi != null);
  143 + assert(dpi > 0);
294 144
295 - final _PrintJob job = _newPrintJob(_PrintJob(  
296 - onPageRasterized: StreamController<PdfRaster>(),  
297 - )); 145 + return PrintingPlatform.instance.raster(document, pages, dpi);
  146 + }
298 147
299 - final Map<String, dynamic> params = <String, dynamic>{  
300 - 'doc': Uint8List.fromList(document),  
301 - 'pages': pages,  
302 - 'scale': dpi / PdfPageFormat.inch,  
303 - 'job': job.index,  
304 - }; 148 + /// Prints a [PdfDocument] or a pdf stream to a local printer
  149 + /// using the platform UI
  150 + @Deprecated('use Printing.layoutPdf(onLayout: (_) => document.save());')
  151 + static Future<void> printPdf({
  152 + @Deprecated('use bytes with document.save()') PdfDocument document,
  153 + List<int> bytes,
  154 + }) async {
  155 + assert(document != null || bytes != null);
  156 + assert(!(document == null && bytes == null));
305 157
306 - _channel.invokeMethod<void>('rasterPdf', params);  
307 - return job.onPageRasterized.stream; 158 + layoutPdf(
  159 + onLayout: (PdfPageFormat format) =>
  160 + document != null ? document.save() : bytes);
308 } 161 }
309 } 162 }
@@ -14,19 +14,10 @@ @@ -14,19 +14,10 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -part of printing;  
18 - 17 +/// Capabilities supported for the current platform
19 class PrintingInfo { 18 class PrintingInfo {
20 - factory PrintingInfo.fromMap(Map<dynamic, dynamic> map) => PrintingInfo._(  
21 - directPrint: map['directPrint'] ?? false,  
22 - dynamicLayout: map['dynamicLayout'] ?? false,  
23 - canPrint: map['canPrint'],  
24 - canConvertHtml: map['canConvertHtml'],  
25 - canShare: map['canShare'],  
26 - canRaster: map['canRaster'],  
27 - );  
28 -  
29 - const PrintingInfo._({ 19 + /// Create an information object
  20 + const PrintingInfo({
30 this.directPrint = false, 21 this.directPrint = false,
31 this.dynamicLayout = false, 22 this.dynamicLayout = false,
32 this.canPrint = false, 23 this.canPrint = false,
@@ -40,13 +31,38 @@ class PrintingInfo { @@ -40,13 +31,38 @@ class PrintingInfo {
40 assert(canShare != null), 31 assert(canShare != null),
41 assert(canRaster != null); 32 assert(canRaster != null);
42 33
43 - static const PrintingInfo unavailable = PrintingInfo._(); 34 + /// Create an information object from a dictionnary
  35 + factory PrintingInfo.fromMap(Map<dynamic, dynamic> map) => PrintingInfo(
  36 + directPrint: map['directPrint'] ?? false,
  37 + dynamicLayout: map['dynamicLayout'] ?? false,
  38 + canPrint: map['canPrint'] ?? false,
  39 + canConvertHtml: map['canConvertHtml'] ?? false,
  40 + canShare: map['canShare'] ?? false,
  41 + canRaster: map['canRaster'] ?? false,
  42 + );
  43 +
  44 + /// Default information with no feature available
  45 + static const PrintingInfo unavailable = PrintingInfo();
44 46
  47 + /// The platform can print directly to a printer
45 final bool directPrint; 48 final bool directPrint;
  49 +
  50 + /// The platform can request a dynamic layout when the user change
  51 + /// the printer or printer settings
46 final bool dynamicLayout; 52 final bool dynamicLayout;
  53 +
  54 + /// The platform implementation is able to print a Pdf document
47 final bool canPrint; 55 final bool canPrint;
  56 +
  57 + /// The platform implementation is able to convert an html document to Pdf
48 final bool canConvertHtml; 58 final bool canConvertHtml;
  59 +
  60 + /// The platform implementation is able to share a Pdf document
  61 + /// to other applications
49 final bool canShare; 62 final bool canShare;
  63 +
  64 + /// The platform implementation is able to convert pages from a Pdf document
  65 + /// to a stream of images
50 final bool canRaster; 66 final bool canRaster;
51 67
52 @override 68 @override
  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:convert';
  19 +import 'dart:html' as html;
  20 +import 'dart:js' as js;
  21 +import 'dart:typed_data';
  22 +import 'dart:ui';
  23 +
  24 +import 'package:flutter/rendering.dart' show Rect;
  25 +import 'package:flutter_web_plugins/flutter_web_plugins.dart';
  26 +import 'package:pdf/pdf.dart';
  27 +import 'package:printing/src/raster.dart';
  28 +import 'package:printing/src/printer.dart';
  29 +
  30 +import 'callback.dart';
  31 +import 'interface.dart';
  32 +import 'printing_info.dart';
  33 +
  34 +class PrintingPlugin extends PrintingPlatform {
  35 + /// Registers this class as the default instance of [PrintingPlugin].
  36 + static void registerWith(Registrar registrar) {
  37 + PrintingPlatform.instance = PrintingPlugin();
  38 + }
  39 +
  40 + @override
  41 + Future<PrintingInfo> info() async {
  42 + return const PrintingInfo(
  43 + directPrint: false,
  44 + dynamicLayout: false,
  45 + canPrint: true,
  46 + canConvertHtml: false,
  47 + canShare: true,
  48 + );
  49 + }
  50 +
  51 + @override
  52 + Future<bool> layoutPdf(
  53 + LayoutCallback onLayout,
  54 + String name,
  55 + PdfPageFormat format,
  56 + ) async {
  57 + final List<int> result = await onLayout(format);
  58 +
  59 + if (result == null || result.isEmpty) {
  60 + return false;
  61 + }
  62 +
  63 + final bool isChrome = js.context['chrome'] != null;
  64 +
  65 + if (!isChrome) {
  66 + final String pr = 'data:application/pdf;base64,${base64.encode(result)}';
  67 + final html.Window win = js.context['window'];
  68 + win.open(pr, name);
  69 +
  70 + return true;
  71 + }
  72 +
  73 + final Completer<bool> completer = Completer<bool>();
  74 + final html.Blob pdfFile = html.Blob(
  75 + <Uint8List>[Uint8List.fromList(result)],
  76 + 'application/pdf',
  77 + );
  78 + final String pdfUrl = html.Url.createObjectUrl(pdfFile);
  79 + final html.HtmlDocument doc = js.context['document'];
  80 + final html.IFrameElement frame = doc.createElement('iframe');
  81 + frame.setAttribute(
  82 + 'style',
  83 + 'visibility: hidden; height: 0; width: 0; position: absolute;',
  84 + // 'height: 400px; width: 600px; position: absolute; z-index: 1000',
  85 + );
  86 +
  87 + frame.setAttribute('src', pdfUrl);
  88 +
  89 + frame.addEventListener('load', (html.Event event) {
  90 + final js.JsObject win =
  91 + js.JsObject.fromBrowserObject(frame)['contentWindow'];
  92 +
  93 + win.callMethod('addEventListener', <dynamic>[
  94 + 'afterprint',
  95 + js.allowInterop<html.EventListener>((html.Event event) {
  96 + frame.remove();
  97 + completer.complete(true);
  98 + }),
  99 + ]);
  100 +
  101 + frame.focus();
  102 + win.callMethod('print');
  103 + });
  104 +
  105 + doc.body.append(frame);
  106 +
  107 + return completer.future;
  108 + }
  109 +
  110 + @override
  111 + Future<bool> sharePdf(
  112 + List<int> bytes,
  113 + String filename,
  114 + Rect bounds,
  115 + ) async {
  116 + final html.Blob pdfFile = html.Blob(
  117 + <Uint8List>[Uint8List.fromList(bytes)],
  118 + 'application/pdf',
  119 + );
  120 + final String pdfUrl = html.Url.createObjectUrl(pdfFile);
  121 + final html.HtmlDocument doc = js.context['document'];
  122 + final html.AnchorElement link = doc.createElement('a');
  123 + link.href = pdfUrl;
  124 + link.download = filename;
  125 + link.click();
  126 + return true;
  127 + }
  128 +
  129 + @override
  130 + Future<List<int>> convertHtml(
  131 + String html,
  132 + String baseUrl,
  133 + PdfPageFormat format,
  134 + ) {
  135 + throw UnimplementedError();
  136 + }
  137 +
  138 + @override
  139 + Future<bool> directPrintPdf(
  140 + Printer printer,
  141 + LayoutCallback onLayout,
  142 + String name,
  143 + PdfPageFormat format,
  144 + ) {
  145 + throw UnimplementedError();
  146 + }
  147 +
  148 + @override
  149 + Future<Printer> pickPrinter(
  150 + Rect bounds,
  151 + ) {
  152 + throw UnimplementedError();
  153 + }
  154 +
  155 + @override
  156 + Stream<PdfRaster> raster(
  157 + List<int> document,
  158 + List<int> pages,
  159 + double dpi,
  160 + ) {
  161 + throw UnimplementedError();
  162 + }
  163 +}
@@ -14,17 +14,28 @@ @@ -14,17 +14,28 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -part of printing; 17 +import 'dart:async';
  18 +import 'dart:typed_data';
  19 +import 'dart:ui' as ui;
18 20
  21 +import 'package:flutter/painting.dart';
  22 +
  23 +/// Represents a bitmap image
19 class PdfRaster { 24 class PdfRaster {
20 - PdfRaster._( 25 + /// Create a bitmap image
  26 + PdfRaster(
21 this.width, 27 this.width,
22 this.height, 28 this.height,
23 this.pixels, 29 this.pixels,
24 ); 30 );
25 31
  32 + /// The width of the image
26 final int width; 33 final int width;
  34 +
  35 + /// The height of the image
27 final int height; 36 final int height;
  37 +
  38 + /// The raw RGBA pixels of the image
28 final Uint8List pixels; 39 final Uint8List pixels;
29 40
30 @override 41 @override
@@ -52,9 +63,12 @@ class PdfRaster { @@ -52,9 +63,12 @@ class PdfRaster {
52 } 63 }
53 } 64 }
54 65
  66 +/// Image provider for a [PdfRaster]
55 class PdfRasterImage extends ImageProvider<PdfRaster> { 67 class PdfRasterImage extends ImageProvider<PdfRaster> {
  68 + /// Create an ImageProvider from a [PdfRaster]
56 PdfRasterImage(this.raster); 69 PdfRasterImage(this.raster);
57 70
  71 + /// The image source
58 final PdfRaster raster; 72 final PdfRaster raster;
59 73
60 Future<ImageInfo> _loadAsync() async { 74 Future<ImageInfo> _loadAsync() async {
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 -part of printing;  
18 -  
19 -@Deprecated('Use `Document` instead')  
20 -class PdfDoc extends Document {  
21 - /// Wrapper for a [Document] with zlib compression enabled by default  
22 - PdfDoc(  
23 - {bool compress = true,  
24 - PdfPageMode pageMode = PdfPageMode.none,  
25 - Theme theme,  
26 - String title,  
27 - String author,  
28 - String creator,  
29 - String subject,  
30 - String keywords,  
31 - String producer})  
32 - : super(  
33 - compress: compress,  
34 - pageMode: pageMode,  
35 - theme: theme,  
36 - title: title,  
37 - author: author,  
38 - creator: creator,  
39 - subject: subject,  
40 - keywords: keywords,  
41 - producer: producer,  
42 - ) {  
43 - Document.debug = debugPaintSizeEnabled;  
44 - }  
45 -}  
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 -part of printing_web;  
18 -  
19 -class _PrintJob {  
20 - _PrintJob(this.printing, this.index);  
21 -  
22 - final PrintingPlugin printing;  
23 - final int index;  
24 -  
25 - String _jobName;  
26 -  
27 - Future<int> printPdf(  
28 - String name,  
29 - double width,  
30 - double height,  
31 - double marginLeft,  
32 - double marginTop,  
33 - double marginRight,  
34 - double marginBottom) async {  
35 - _jobName = name;  
36 - await printing.onLayout(  
37 - this, width, height, marginLeft, marginTop, marginRight, marginBottom);  
38 - return 1;  
39 - }  
40 -  
41 - static Future<int> sharePdf(List<int> data, double x, double y, double width,  
42 - double height, String name) async {  
43 - final html.Blob pdfFile = html.Blob(<dynamic>[data], 'application/pdf');  
44 - final String pdfUrl = html.Url.createObjectUrl(pdfFile);  
45 - final html.HtmlDocument doc = js.context['document'];  
46 - final html.AnchorElement link = doc.createElement('a');  
47 - link.href = pdfUrl;  
48 - link.download = name;  
49 - link.click();  
50 - return 1;  
51 - }  
52 -  
53 - static Map<String, dynamic> printingInfo() {  
54 - return <String, dynamic>{  
55 - 'directPrint': false,  
56 - 'dynamicLayout': false,  
57 - 'canPrint': true,  
58 - 'canConvertHtml': false,  
59 - 'canShare': true,  
60 - };  
61 - }  
62 -  
63 - void setDocument(List<int> result) {  
64 - final bool isChrome = js.context['chrome'] != null;  
65 -  
66 - if (!isChrome) {  
67 - sharePdf(result, 0, 0, 0, 0, _jobName + '.pdf');  
68 - printing.onCompleted(this, true);  
69 - return;  
70 - }  
71 -  
72 - final html.Blob pdfFile = html.Blob(<dynamic>[result], 'application/pdf');  
73 - final String pdfUrl = html.Url.createObjectUrl(pdfFile);  
74 - final html.HtmlDocument doc = js.context['document'];  
75 - final html.IFrameElement frame = doc.createElement('iframe');  
76 - frame.setAttribute('style',  
77 - 'visibility: hidden; height: 0; width: 0; position: absolute;');  
78 - frame.setAttribute('src', pdfUrl);  
79 - doc.body.append(frame);  
80 -  
81 - frame.addEventListener('load', (html.Event event) {  
82 - final js.JsObject win =  
83 - js.JsObject.fromBrowserObject(frame)['contentWindow'];  
84 -  
85 - win.callMethod('addEventListener', <dynamic>[  
86 - 'afterprint',  
87 - js.allowInterop<html.EventListener>((html.Event event) {  
88 - frame.remove();  
89 - printing.onCompleted(this, true);  
90 - }),  
91 - ]);  
92 -  
93 - frame.focus();  
94 - win.callMethod('print');  
95 - printing.onCompleted(this, true);  
96 - });  
97 - }  
98 -  
99 - Future<void> cancelJob() async {}  
100 -}  
@@ -4,7 +4,7 @@ description: Plugin that allows Flutter apps to generate and print documents to @@ -4,7 +4,7 @@ description: Plugin that allows Flutter apps to generate and print documents to
4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing 4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing
5 repository: https://github.com/DavBfr/dart_pdf 5 repository: https://github.com/DavBfr/dart_pdf
6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues 6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues
7 -version: 3.1.0 7 +version: 3.2.0
8 8
9 environment: 9 environment:
10 sdk: ">=2.3.0 <3.0.0" 10 sdk: ">=2.3.0 <3.0.0"
@@ -15,11 +15,13 @@ dependencies: @@ -15,11 +15,13 @@ dependencies:
15 sdk: flutter 15 sdk: flutter
16 flutter_web_plugins: 16 flutter_web_plugins:
17 sdk: flutter 17 sdk: flutter
18 - pdf: "^1.3.15" 18 + pdf: ^1.3.15
  19 + plugin_platform_interface: ^1.0.2
19 20
20 dev_dependencies: 21 dev_dependencies:
21 flutter_test: 22 flutter_test:
22 sdk: flutter 23 sdk: flutter
  24 + mockito: ^4.1.1
23 25
24 dependency_overrides: 26 dependency_overrides:
25 pdf: 27 pdf:
@@ -36,5 +38,5 @@ flutter: @@ -36,5 +38,5 @@ flutter:
36 macos: 38 macos:
37 pluginClass: PrintingPlugin 39 pluginClass: PrintingPlugin
38 web: 40 web:
39 - fileName: printing_web.dart 41 + fileName: src/printing_web.dart
40 pluginClass: PrintingPlugin 42 pluginClass: PrintingPlugin
  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:io';
  18 +import 'dart:typed_data';
  19 +
  20 +import 'package:flutter/services.dart';
  21 +import 'package:flutter/widgets.dart';
  22 +import 'package:flutter_test/flutter_test.dart';
  23 +import 'package:pdf/pdf.dart';
  24 +import 'package:pdf/widgets.dart' as pdf;
  25 +import 'package:printing/printing.dart';
  26 +
  27 +pdf.Document doc;
  28 +pdf.Font ttf;
  29 +
  30 +void main() {
  31 + final String path =
  32 + Directory.current.path.split('/').last == 'test' ? '..' : '.';
  33 + const MethodChannel channel = MethodChannel('net.nfet.printing');
  34 + TestWidgetsFlutterBinding.ensureInitialized();
  35 +
  36 + setUp(() {
  37 + channel.setMockMethodCallHandler((MethodCall methodCall) async {
  38 + print(methodCall);
  39 + return '1';
  40 + });
  41 + });
  42 +
  43 + tearDown(() {
  44 + channel.setMockMethodCallHandler(null);
  45 + });
  46 +
  47 + test('convertHtml', () async {
  48 + // expect(await Printing.platformVersion, '42');
  49 + });
  50 +
  51 + test('pdfImageFromImageProvider(FileImage)', () async {
  52 + final PdfImage image = await pdfImageFromImageProvider(
  53 + pdf: doc.document, image: FileImage(File('$path/example.png')));
  54 +
  55 + doc.addPage(
  56 + pdf.Page(
  57 + build: (pdf.Context context) => pdf.Center(
  58 + child: pdf.Container(
  59 + child: pdf.Image(image),
  60 + ),
  61 + ),
  62 + ),
  63 + );
  64 + });
  65 +
  66 + setUpAll(() {
  67 + pdf.Document.debug = true;
  68 + pdf.RichText.debug = true;
  69 + final Uint8List fontData =
  70 + File('$path/../pdf/open-sans.ttf').readAsBytesSync();
  71 + ttf = pdf.Font.ttf(fontData.buffer.asByteData());
  72 + doc = pdf.Document();
  73 + });
  74 +
  75 + tearDownAll(() {
  76 + final File file = File('printing.pdf');
  77 + file.writeAsBytesSync(doc.save());
  78 + });
  79 +}
  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_test/flutter_test.dart';
  18 +import 'package:printing/printing.dart';
  19 +
  20 +void main() {
  21 + setUp(() {
  22 + TestWidgetsFlutterBinding.ensureInitialized();
  23 + });
  24 +
  25 + test('PrintingInfo', () async {
  26 + final PrintingInfo info = PrintingInfo.unavailable;
  27 + expect(info.canConvertHtml, false);
  28 + expect(info.directPrint, false);
  29 + expect(info.dynamicLayout, false);
  30 + expect(info.canPrint, false);
  31 + expect(info.canConvertHtml, false);
  32 + expect(info.canShare, false);
  33 + expect(info.canRaster, false);
  34 +
  35 + expect(info.toString(), isA<String>());
  36 + });
  37 +
  38 + test('PrintingInfo.fromMap', () async {
  39 + final PrintingInfo info = PrintingInfo.fromMap(
  40 + <dynamic, dynamic>{
  41 + 'canPrint': true,
  42 + },
  43 + );
  44 +
  45 + expect(info.canConvertHtml, false);
  46 + expect(info.directPrint, false);
  47 + expect(info.dynamicLayout, false);
  48 + expect(info.canPrint, true);
  49 + expect(info.canConvertHtml, false);
  50 + expect(info.canShare, false);
  51 + expect(info.canRaster, false);
  52 + });
  53 +}
1 -import 'dart:io';  
2 -import 'dart:typed_data'; 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 + */
3 16
4 -import 'package:flutter/services.dart';  
5 -import 'package:flutter/widgets.dart';  
6 import 'package:flutter_test/flutter_test.dart'; 17 import 'package:flutter_test/flutter_test.dart';
  18 +import 'package:mockito/mockito.dart';
7 import 'package:pdf/pdf.dart'; 19 import 'package:pdf/pdf.dart';
8 -import 'package:pdf/widgets.dart' as pdf; 20 +import 'package:plugin_platform_interface/plugin_platform_interface.dart';
9 import 'package:printing/printing.dart'; 21 import 'package:printing/printing.dart';
10 -  
11 -pdf.Document doc;  
12 -pdf.Font ttf; 22 +import 'package:printing/src/interface.dart';
13 23
14 void main() { 24 void main() {
15 - final String path =  
16 - Directory.current.path.split('/').last == 'test' ? '..' : '.';  
17 - const MethodChannel channel = MethodChannel('net.nfet.printing');  
18 - TestWidgetsFlutterBinding.ensureInitialized();  
19 -  
20 setUp(() { 25 setUp(() {
21 - channel.setMockMethodCallHandler((MethodCall methodCall) async {  
22 - print(methodCall);  
23 - return '1';  
24 - }); 26 + TestWidgetsFlutterBinding.ensureInitialized();
  27 + final MockPrinting mock = MockPrinting();
  28 + PrintingPlatform.instance = mock;
25 }); 29 });
26 30
27 - tearDown(() {  
28 - channel.setMockMethodCallHandler(null); 31 + test('info', () async {
  32 + final PrintingInfo info = await Printing.info();
  33 + expect(info, null);
29 }); 34 });
30 35
31 - test('convertHtml', () async {  
32 - // expect(await Printing.platformVersion, '42'); 36 + test('layoutPdf', () async {
  37 + expect(
  38 + () async => await Printing.layoutPdf(onLayout: null),
  39 + throwsAssertionError,
  40 + );
  41 +
  42 + expect(
  43 + await Printing.layoutPdf(
  44 + onLayout: (_) => null,
  45 + name: 'doc',
  46 + format: PdfPageFormat.letter,
  47 + ),
  48 + null);
33 }); 49 });
34 50
35 - test('pdfImageFromImageProvider(FileImage)', () async {  
36 - final PdfImage image = await pdfImageFromImageProvider(  
37 - pdf: doc.document, image: FileImage(File('$path/example.png'))); 51 + test('sharePdf', () async {
  52 + expect(
  53 + () async => await Printing.sharePdf(bytes: null),
  54 + throwsAssertionError,
  55 + );
38 56
39 - doc.addPage(  
40 - pdf.Page(  
41 - build: (pdf.Context context) => pdf.Center(  
42 - child: pdf.Container(  
43 - child: pdf.Image(image),  
44 - ),  
45 - ), 57 + expect(
  58 + await Printing.sharePdf(
  59 + bytes: <int>[],
  60 + ),
  61 + null,
  62 + );
  63 + });
  64 +
  65 + test('pickPrinter', () async {
  66 + expect(
  67 + await Printing.pickPrinter(),
  68 + null,
  69 + );
  70 + });
  71 +
  72 + test('directPrintPdf', () async {
  73 + expect(
  74 + await Printing.directPrintPdf(onLayout: null, printer: null),
  75 + false,
  76 + );
  77 +
  78 + expect(
  79 + () async => await Printing.directPrintPdf(
  80 + onLayout: null,
  81 + printer: const Printer(url: 'test'),
  82 + ),
  83 + throwsAssertionError,
  84 + );
  85 +
  86 + expect(
  87 + await Printing.directPrintPdf(
  88 + onLayout: (_) => null,
  89 + printer: const Printer(url: 'test'),
46 ), 90 ),
  91 + null,
47 ); 92 );
48 }); 93 });
49 94
50 - setUpAll(() {  
51 - pdf.Document.debug = true;  
52 - pdf.RichText.debug = true;  
53 - final Uint8List fontData =  
54 - File('$path/../pdf/open-sans.ttf').readAsBytesSync();  
55 - ttf = pdf.Font.ttf(fontData.buffer.asByteData());  
56 - doc = pdf.Document(); 95 + test('convertHtml', () async {
  96 + expect(
  97 + await Printing.convertHtml(html: '<html></html>'),
  98 + null,
  99 + );
57 }); 100 });
58 101
59 - tearDownAll(() {  
60 - final File file = File('printing.pdf');  
61 - file.writeAsBytesSync(doc.save()); 102 + test('raster', () async {
  103 + expect(
  104 + () => Printing.raster(null),
  105 + throwsAssertionError,
  106 + );
  107 +
  108 + expect(
  109 + Printing.raster(<int>[]),
  110 + null,
  111 + );
62 }); 112 });
63 } 113 }
  114 +
  115 +class MockPrinting extends Mock
  116 + with MockPlatformInterfaceMixin
  117 + implements PrintingPlatform {}
  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:typed_data';
  18 +import 'dart:ui' as ui;
  19 +
  20 +import 'package:flutter/widgets.dart';
  21 +import 'package:flutter_test/flutter_test.dart';
  22 +import 'package:printing/printing.dart';
  23 +
  24 +void main() {
  25 + setUp(() {
  26 + TestWidgetsFlutterBinding.ensureInitialized();
  27 + });
  28 +
  29 + test('PdfRaster', () async {
  30 + final PdfRaster raster =
  31 + PdfRaster(10, 10, Uint8List.fromList(List<int>.filled(10 * 10 * 4, 0)));
  32 + expect(raster.toString(), 'Image 10x10 400 bytes');
  33 + expect(await raster.toImage(), isA<ui.Image>());
  34 + expect(await raster.toPng(), isA<Uint8List>());
  35 + });
  36 +
  37 + testWidgets('PdfRasterImage', (WidgetTester tester) async {
  38 + final PdfRaster raster =
  39 + PdfRaster(10, 10, Uint8List.fromList(List<int>.filled(10 * 10 * 4, 0)));
  40 +
  41 + await tester.pumpWidget(Image(image: PdfRasterImage(raster)));
  42 + await tester.pumpAndSettle();
  43 + });
  44 +}