David PHAM-VAN

Implement Printing.raster() on Flutter Web

@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 ## 3.4.0 3 ## 3.4.0
4 4
5 - Add PdfPreview Widget 5 - Add PdfPreview Widget
  6 +- Implement Printing.raster() on Flutter Web
6 7
7 ## 3.3.1 8 ## 3.3.1
8 9
@@ -26,6 +26,10 @@ @@ -26,6 +26,10 @@
26 }); 26 });
27 } 27 }
28 </script> 28 </script>
  29 + <script src="//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.min.js"></script>
  30 + <script type="text/javascript">
  31 + pdfjsLib.GlobalWorkerOptions.workerSrc = "//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.worker.min.js";
  32 + </script>
29 <script src="main.dart.js" type="application/javascript"></script> 33 <script src="main.dart.js" type="application/javascript"></script>
30 </body> 34 </body>
31 </html> 35 </html>
  1 +@JS()
  2 +library pdf.js;
  3 +
  4 +import 'dart:html';
  5 +import 'dart:typed_data';
  6 +
  7 +import 'package:js/js.dart';
  8 +
  9 +@JS('pdfjsLib')
  10 +class PdfJs {
  11 + external static PdfJsDocLoader getDocument(Settings data);
  12 +}
  13 +
  14 +@anonymous
  15 +@JS()
  16 +class Settings {
  17 + external set data(Uint8List value);
  18 + external set scale(double value);
  19 + external set canvasContext(CanvasRenderingContext2D value);
  20 + external set viewport(PdfJsViewport value);
  21 +}
  22 +
  23 +@anonymous
  24 +@JS()
  25 +class PdfJsDocLoader {
  26 + external Future<PdfJsDoc> get promise;
  27 +}
  28 +
  29 +@anonymous
  30 +@JS()
  31 +class PdfJsDoc {
  32 + external Future<PdfJsPage> getPage(int num);
  33 + external int get numPages;
  34 +}
  35 +
  36 +@anonymous
  37 +@JS()
  38 +class PdfJsPage {
  39 + external PdfJsViewport getViewport(Settings data);
  40 + external PdfJsRender render(Settings data);
  41 +}
  42 +
  43 +@anonymous
  44 +@JS()
  45 +class PdfJsViewport {
  46 + external num get width;
  47 + external num get height;
  48 +}
  49 +
  50 +@anonymous
  51 +@JS()
  52 +class PdfJsRender {
  53 + external Future<void> get promise;
  54 +}
@@ -17,13 +17,18 @@ @@ -17,13 +17,18 @@
17 import 'dart:async'; 17 import 'dart:async';
18 import 'dart:convert'; 18 import 'dart:convert';
19 import 'dart:html' as html; 19 import 'dart:html' as html;
  20 +import 'dart:html';
  21 +import 'dart:io';
20 import 'dart:js' as js; 22 import 'dart:js' as js;
  23 +import 'dart:js_util';
21 import 'dart:typed_data'; 24 import 'dart:typed_data';
22 import 'dart:ui'; 25 import 'dart:ui';
23 26
24 import 'package:flutter/rendering.dart' show Rect; 27 import 'package:flutter/rendering.dart' show Rect;
25 import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 28 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
  29 +import 'package:image/image.dart' as im;
26 import 'package:pdf/pdf.dart'; 30 import 'package:pdf/pdf.dart';
  31 +import 'package:printing/src/pdfjs.dart';
27 import 'package:printing/src/printer.dart'; 32 import 'package:printing/src/printer.dart';
28 import 'package:printing/src/raster.dart'; 33 import 'package:printing/src/raster.dart';
29 34
@@ -41,12 +46,17 @@ class PrintingPlugin extends PrintingPlatform { @@ -41,12 +46,17 @@ class PrintingPlugin extends PrintingPlatform {
41 46
42 @override 47 @override
43 Future<PrintingInfo> info() async { 48 Future<PrintingInfo> info() async {
44 - return const PrintingInfo( 49 + final dynamic workerSrc = js.context.callMethod('eval', <String>[
  50 + 'typeof pdfjsLib !== "undefined" && pdfjsLib.GlobalWorkerOptions.workerSrc!="";'
  51 + ]);
  52 +
  53 + return PrintingInfo(
45 directPrint: false, 54 directPrint: false,
46 dynamicLayout: false, 55 dynamicLayout: false,
47 canPrint: true, 56 canPrint: true,
48 canConvertHtml: false, 57 canConvertHtml: false,
49 canShare: true, 58 canShare: true,
  59 + canRaster: workerSrc,
50 ); 60 );
51 } 61 }
52 62
@@ -160,7 +170,99 @@ class PrintingPlugin extends PrintingPlatform { @@ -160,7 +170,99 @@ class PrintingPlugin extends PrintingPlatform {
160 Uint8List document, 170 Uint8List document,
161 List<int> pages, 171 List<int> pages,
162 double dpi, 172 double dpi,
163 - ) {  
164 - throw UnimplementedError(); 173 + ) async* {
  174 + final PdfJsDocLoader t = PdfJs.getDocument(Settings()..data = document);
  175 +
  176 + final PdfJsDoc d = await promiseToFuture(t.promise);
  177 + final int numPages = d.numPages;
  178 +
  179 + final html.CanvasElement canvas =
  180 + js.context['document'].createElement('canvas');
  181 + final html.CanvasRenderingContext2D context = canvas.getContext('2d');
  182 +
  183 + for (int i = 0; i < numPages; i++) {
  184 + final PdfJsPage page = await promiseToFuture(d.getPage(i + 1));
  185 + final PdfJsViewport viewport = page.getViewport(Settings()..scale = 1.5);
  186 +
  187 + canvas.height = viewport.height.toInt();
  188 + canvas.width = viewport.width.toInt();
  189 +
  190 + final Settings renderContext = Settings()
  191 + ..canvasContext = context
  192 + ..viewport = viewport;
  193 +
  194 + await promiseToFuture<void>(page.render(renderContext).promise);
  195 +
  196 + // final Uint8ClampedList data =
  197 + // context.getImageData(0, 0, canvas.width, canvas.height).data;
  198 +
  199 + // Convert the image to PNG
  200 + final Completer<void> completer = Completer<void>();
  201 + final html.Blob blob = await canvas.toBlob();
  202 + final BytesBuilder data = BytesBuilder();
  203 + final html.FileReader r = FileReader();
  204 + r.readAsArrayBuffer(blob);
  205 + r.onLoadEnd.listen(
  206 + (ProgressEvent e) {
  207 + data.add(r.result);
  208 + completer.complete();
  209 + },
  210 + );
  211 + await completer.future;
  212 +
  213 + yield _WebPdfRaster(
  214 + canvas.width,
  215 + canvas.height,
  216 + data.toBytes(),
  217 + );
  218 + }
  219 + }
  220 +}
  221 +
  222 +class _WebPdfRaster extends PdfRaster {
  223 + _WebPdfRaster(
  224 + int width,
  225 + int height,
  226 + this.png,
  227 + ) : super(width, height, null);
  228 +
  229 + final Uint8List png;
  230 +
  231 + Uint8List _pixels;
  232 +
  233 + @override
  234 + Uint8List get pixels {
  235 + if (_pixels == null) {
  236 + final im.Image img = asImage();
  237 + _pixels = img.data.buffer.asUint8List();
  238 + }
  239 +
  240 + return _pixels;
  241 + }
  242 +
  243 + @override
  244 + Future<Image> toImage() {
  245 + final Completer<Image> comp = Completer<Image>();
  246 + decodeImageFromPixels(
  247 + png,
  248 + width,
  249 + height,
  250 + PixelFormat.rgba8888,
  251 + (Image image) => comp.complete(image),
  252 + );
  253 + return comp.future;
  254 + }
  255 +
  256 + @override
  257 + Future<Uint8List> toPng() async {
  258 + return png;
  259 + }
  260 +
  261 + @override
  262 + im.Image asImage() {
  263 + if (_pixels != null) {
  264 + return super.asImage();
  265 + }
  266 + return im.PngDecoder().decodeImage(png);
165 } 267 }
166 } 268 }
@@ -24,7 +24,7 @@ import 'package:image/image.dart' as im; @@ -24,7 +24,7 @@ import 'package:image/image.dart' as im;
24 /// Represents a bitmap image 24 /// Represents a bitmap image
25 class PdfRaster { 25 class PdfRaster {
26 /// Create a bitmap image 26 /// Create a bitmap image
27 - PdfRaster( 27 + const PdfRaster(
28 this.width, 28 this.width,
29 this.height, 29 this.height,
30 this.pixels, 30 this.pixels,
@@ -40,7 +40,7 @@ class PdfRaster { @@ -40,7 +40,7 @@ class PdfRaster {
40 final Uint8List pixels; 40 final Uint8List pixels;
41 41
42 @override 42 @override
43 - String toString() => 'Image ${width}x$height ${pixels.lengthInBytes} bytes'; 43 + String toString() => 'Image ${width}x$height ${width * height * 4} bytes';
44 44
45 /// Decode RGBA raw image to dart:ui Image 45 /// Decode RGBA raw image to dart:ui Image
46 Future<ui.Image> toImage() { 46 Future<ui.Image> toImage() {
@@ -19,6 +19,7 @@ dependencies: @@ -19,6 +19,7 @@ dependencies:
19 plugin_platform_interface: ^1.0.2 19 plugin_platform_interface: ^1.0.2
20 meta: ^1.1.5 20 meta: ^1.1.5
21 image: ^2.1.4 21 image: ^2.1.4
  22 + js: ^0.6.1
22 23
23 dev_dependencies: 24 dev_dependencies:
24 flutter_test: 25 flutter_test: