David PHAM-VAN

Add Raster PDF to Image

1 # Changelog 1 # Changelog
2 2
  3 +## 3.0.2
  4 +
  5 +- Add Raster PDF to Image
  6 +
3 ## 3.0.1 7 ## 3.0.1
4 8
5 - Add a link to the Web example 9 - Add a link to the Web example
@@ -109,7 +109,7 @@ await Printing.layoutPdf( @@ -109,7 +109,7 @@ await Printing.layoutPdf(
109 ```java 109 ```java
110 defaultConfig { 110 defaultConfig {
111 ... 111 ...
112 - minSdkVersion 19 // <-- Change this line to 19 or more 112 + minSdkVersion 21 // <-- Change this line to 21 or more
113 ... 113 ...
114 } 114 }
115 ``` 115 ```
@@ -18,11 +18,15 @@ package net.nfet.flutter.printing; @@ -18,11 +18,15 @@ package net.nfet.flutter.printing;
18 18
19 import android.content.Context; 19 import android.content.Context;
20 import android.content.Intent; 20 import android.content.Intent;
  21 +import android.graphics.Bitmap;
  22 +import android.graphics.Matrix;
  23 +import android.graphics.pdf.PdfRenderer;
21 import android.net.Uri; 24 import android.net.Uri;
22 import android.os.Build; 25 import android.os.Build;
23 import android.os.Bundle; 26 import android.os.Bundle;
24 import android.os.CancellationSignal; 27 import android.os.CancellationSignal;
25 -import android.os.Environment; 28 +import android.os.Handler;
  29 +import android.os.Looper;
26 import android.os.ParcelFileDescriptor; 30 import android.os.ParcelFileDescriptor;
27 import android.print.PageRange; 31 import android.print.PageRange;
28 import android.print.PdfConvert; 32 import android.print.PdfConvert;
@@ -32,16 +36,20 @@ import android.print.PrintDocumentInfo; @@ -32,16 +36,20 @@ import android.print.PrintDocumentInfo;
32 import android.print.PrintJob; 36 import android.print.PrintJob;
33 import android.print.PrintJobInfo; 37 import android.print.PrintJobInfo;
34 import android.print.PrintManager; 38 import android.print.PrintManager;
  39 +import android.util.Log;
35 import android.webkit.WebView; 40 import android.webkit.WebView;
36 import android.webkit.WebViewClient; 41 import android.webkit.WebViewClient;
37 42
38 import androidx.annotation.NonNull; 43 import androidx.annotation.NonNull;
  44 +import androidx.annotation.RequiresApi;
39 import androidx.core.content.FileProvider; 45 import androidx.core.content.FileProvider;
40 46
41 import java.io.File; 47 import java.io.File;
  48 +import java.io.FileInputStream;
42 import java.io.FileOutputStream; 49 import java.io.FileOutputStream;
43 import java.io.IOException; 50 import java.io.IOException;
44 import java.io.OutputStream; 51 import java.io.OutputStream;
  52 +import java.nio.ByteBuffer;
45 import java.util.HashMap; 53 import java.util.HashMap;
46 54
47 /** 55 /**
@@ -71,6 +79,7 @@ public class PrintingJob extends PrintDocumentAdapter { @@ -71,6 +79,7 @@ public class PrintingJob extends PrintDocumentAdapter {
71 result.put("canPrint", true); 79 result.put("canPrint", true);
72 result.put("canConvertHtml", true); 80 result.put("canConvertHtml", true);
73 result.put("canShare", true); 81 result.put("canShare", true);
  82 + result.put("canRaster", true);
74 return result; 83 return result;
75 } 84 }
76 85
@@ -240,4 +249,79 @@ public class PrintingJob extends PrintDocumentAdapter { @@ -240,4 +249,79 @@ public class PrintingJob extends PrintDocumentAdapter {
240 // Content layout reflow is complete 249 // Content layout reflow is complete
241 callback.onLayoutFinished(info, true); 250 callback.onLayoutFinished(info, true);
242 } 251 }
  252 +
  253 + void rasterPdf(final byte[] data, final int[] pages, final Double scale) {
  254 + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
  255 + Log.e("PDF", "PDF Raster available since Android 5.0 Lollipop (API 21)");
  256 + printing.onPageRasterEnd(this);
  257 + return;
  258 + }
  259 +
  260 + Thread thread = new Thread(new Runnable() {
  261 + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
  262 + @Override
  263 + public void run() {
  264 + try {
  265 + File file = File.createTempFile("printing", null, null);
  266 + FileOutputStream oStream = new FileOutputStream(file);
  267 + oStream.write(data);
  268 + oStream.close();
  269 +
  270 + FileInputStream iStream = new FileInputStream(file);
  271 + ParcelFileDescriptor parcelFD = ParcelFileDescriptor.dup(iStream.getFD());
  272 + PdfRenderer renderer = new PdfRenderer(parcelFD);
  273 +
  274 + if (!file.delete()) {
  275 + Log.e("PDF", "Unable to delete temporary file");
  276 + }
  277 +
  278 + final int pageCount = pages != null ? pages.length : renderer.getPageCount();
  279 + for (int i = 0; i < pageCount; i++) {
  280 + PdfRenderer.Page page = renderer.openPage(pages == null ? i : pages[i]);
  281 +
  282 + final int width = Double.valueOf(page.getWidth() * scale).intValue();
  283 + final int height = Double.valueOf(page.getHeight() * scale).intValue();
  284 + int stride = width * 4;
  285 +
  286 + Matrix transform = new Matrix();
  287 + transform.setScale(scale.floatValue(), scale.floatValue());
  288 +
  289 + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  290 +
  291 + page.render(
  292 + bitmap, null, transform, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
  293 +
  294 + page.close();
  295 +
  296 + final ByteBuffer buf = ByteBuffer.allocate(stride * height);
  297 + bitmap.copyPixelsToBuffer(buf);
  298 + bitmap.recycle();
  299 +
  300 + new Handler(Looper.getMainLooper()).post(new Runnable() {
  301 + @Override
  302 + public void run() {
  303 + printing.onPageRasterized(
  304 + PrintingJob.this, buf.array(), width, height);
  305 + }
  306 + });
  307 + }
  308 +
  309 + renderer.close();
  310 + iStream.close();
  311 +
  312 + } catch (IOException e) {
  313 + e.printStackTrace();
  314 + }
  315 +
  316 + new Handler(Looper.getMainLooper()).post(new Runnable() {
  317 + @Override
  318 + public void run() {
  319 + printing.onPageRasterEnd(PrintingJob.this);
  320 + }
  321 + });
  322 + }
  323 + });
  324 +
  325 + thread.start();
  326 + }
243 } 327 }
@@ -125,6 +125,16 @@ public class PrintingPlugin implements MethodCallHandler { @@ -125,6 +125,16 @@ public class PrintingPlugin implements MethodCallHandler {
125 result.success(PrintingJob.printingInfo()); 125 result.success(PrintingJob.printingInfo());
126 break; 126 break;
127 } 127 }
  128 + case "rasterPdf": {
  129 + final byte[] document = call.argument("doc");
  130 + final int[] pages = call.argument("pages");
  131 + Double scale = call.argument("scale");
  132 + final PrintingJob printJob =
  133 + new PrintingJob(activity, this, (int) call.argument("job"));
  134 + printJob.rasterPdf(document, pages, scale);
  135 + result.success(1);
  136 + break;
  137 + }
128 default: 138 default:
129 result.notImplemented(); 139 result.notImplemented();
130 break; 140 break;
@@ -194,4 +204,22 @@ public class PrintingPlugin implements MethodCallHandler { @@ -194,4 +204,22 @@ public class PrintingPlugin implements MethodCallHandler {
194 204
195 channel.invokeMethod("onHtmlError", args); 205 channel.invokeMethod("onHtmlError", args);
196 } 206 }
  207 +
  208 + /// send pdf to raster data result to flutter
  209 + void onPageRasterized(PrintingJob printJob, byte[] imageData, int width, int height) {
  210 + HashMap<String, Object> args = new HashMap<>();
  211 + args.put("image", imageData);
  212 + args.put("width", width);
  213 + args.put("height", height);
  214 + args.put("job", printJob.index);
  215 +
  216 + channel.invokeMethod("onPageRasterized", args);
  217 + }
  218 +
  219 + void onPageRasterEnd(PrintingJob printJob) {
  220 + HashMap<String, Object> args = new HashMap<>();
  221 + args.put("job", printJob.index);
  222 +
  223 + channel.invokeMethod("onPageRasterEnd", args);
  224 + }
197 } 225 }
@@ -34,7 +34,7 @@ android { @@ -34,7 +34,7 @@ android {
34 defaultConfig { 34 defaultConfig {
35 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 35 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 applicationId "com.example.example" 36 applicationId "com.example.example"
37 - minSdkVersion 19 37 + minSdkVersion 21
38 targetSdkVersion 28 38 targetSdkVersion 28
39 versionCode flutterVersionCode.toInteger() 39 versionCode flutterVersionCode.toInteger()
40 versionName flutterVersionName 40 versionName flutterVersionName
  1 +import 'package:flutter/material.dart';
  2 +
  3 +class ImageViewer extends StatelessWidget {
  4 + const ImageViewer({this.images});
  5 +
  6 + final List<ImageProvider> images;
  7 +
  8 + Widget buildImage(BuildContext context, ImageProvider image) {
  9 + return Card(
  10 + margin: const EdgeInsets.all(10),
  11 + child: FittedBox(
  12 + child: Image(image: image),
  13 + ));
  14 + }
  15 +
  16 + @override
  17 + Widget build(BuildContext context) {
  18 + return Scaffold(
  19 + appBar: AppBar(
  20 + title: const Text('Document as Images'),
  21 + ),
  22 + body: ListView.builder(
  23 + itemCount: images.length,
  24 + itemBuilder: (BuildContext context, int index) =>
  25 + buildImage(context, images[index]),
  26 + ),
  27 + );
  28 + }
  29 +}
@@ -15,6 +15,7 @@ import 'package:pdf/widgets.dart' as pdf; @@ -15,6 +15,7 @@ import 'package:pdf/widgets.dart' as pdf;
15 import 'package:printing/printing.dart'; 15 import 'package:printing/printing.dart';
16 16
17 import 'document.dart'; 17 import 'document.dart';
  18 +import 'image_viewer.dart';
18 import 'viewer.dart'; 19 import 'viewer.dart';
19 20
20 void main() { 21 void main() {
@@ -82,6 +83,22 @@ class MyAppState extends State<MyApp> { @@ -82,6 +83,22 @@ class MyAppState extends State<MyApp> {
82 ); 83 );
83 } 84 }
84 85
  86 + Future<void> _rasterToImage() async {
  87 + final List<int> doc = (await generateDocument(PdfPageFormat.a4)).save();
  88 +
  89 + final List<ImageProvider> images = <ImageProvider>[];
  90 +
  91 + await for (PdfRaster page in Printing.raster(doc)) {
  92 + images.add(PdfRasterImage(page));
  93 + }
  94 +
  95 + Navigator.push<dynamic>(
  96 + context,
  97 + MaterialPageRoute<dynamic>(
  98 + builder: (BuildContext context) => ImageViewer(images: images)),
  99 + );
  100 + }
  101 +
85 Future<void> _pickPrinter() async { 102 Future<void> _pickPrinter() async {
86 print('Pick printer ...'); 103 print('Pick printer ...');
87 104
@@ -243,6 +260,11 @@ class MyAppState extends State<MyApp> { @@ -243,6 +260,11 @@ class MyAppState extends State<MyApp> {
243 RaisedButton( 260 RaisedButton(
244 child: const Text('Save to file'), onPressed: _saveAsFile), 261 child: const Text('Save to file'), onPressed: _saveAsFile),
245 RaisedButton( 262 RaisedButton(
  263 + child: const Text('Raster to Image'),
  264 + onPressed:
  265 + printingInfo?.canRaster ?? false ? _rasterToImage : null,
  266 + ),
  267 + RaisedButton(
246 child: const Text('Print Html'), 268 child: const Text('Print Html'),
247 onPressed: 269 onPressed:
248 printingInfo?.canConvertHtml ?? false ? _printHtml : null, 270 printingInfo?.canConvertHtml ?? false ? _printHtml : null,
@@ -231,6 +231,41 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -231,6 +231,41 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
231 controller.present(animated: true, completionHandler: pickPrinterCompletionHandler) 231 controller.present(animated: true, completionHandler: pickPrinterCompletionHandler)
232 } 232 }
233 233
  234 + public func rasterPdf(data: Data, pages: [Int]?, scale: CGFloat) {
  235 + let provider = CGDataProvider(data: data as CFData)!
  236 + let document = CGPDFDocument(provider)!
  237 +
  238 + DispatchQueue.global().async {
  239 + let pageCount = document.numberOfPages
  240 +
  241 + for pageNum in pages ?? Array(0 ... pageCount - 1) {
  242 + guard let page = document.page(at: pageNum + 1) else { continue }
  243 + let rect = page.getBoxRect(.mediaBox)
  244 + let width = Int(rect.width * scale)
  245 + let height = Int(rect.height * scale)
  246 + let stride = width * 4
  247 + var data = Data(repeating: 0, count: stride * height)
  248 +
  249 + data.withUnsafeMutableBytes { (ptr: UnsafeMutablePointer<UInt8>) in
  250 + let rgb = CGColorSpaceCreateDeviceRGB()
  251 + let context = CGContext(data: ptr, width: width, height: height, bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
  252 + if context != nil {
  253 + context!.scaleBy(x: scale, y: scale)
  254 + context!.drawPDFPage(page)
  255 + }
  256 + }
  257 +
  258 + DispatchQueue.main.sync {
  259 + self.printing.onPageRasterized(printJob: self, imageData: data, width: width, height: height)
  260 + }
  261 + }
  262 +
  263 + DispatchQueue.main.sync {
  264 + self.printing.onPageRasterEnd(printJob: self)
  265 + }
  266 + }
  267 + }
  268 +
234 public static func printingInfo() -> NSDictionary { 269 public static func printingInfo() -> NSDictionary {
235 let data: NSDictionary = [ 270 let data: NSDictionary = [
236 "directPrint": true, 271 "directPrint": true,
@@ -238,6 +273,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -238,6 +273,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
238 "canPrint": true, 273 "canPrint": true,
239 "canConvertHtml": true, 274 "canConvertHtml": true,
240 "canShare": true, 275 "canShare": true,
  276 + "canRaster": true,
241 ] 277 ]
242 return data 278 return data
243 } 279 }
@@ -111,6 +111,15 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -111,6 +111,15 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
111 )) 111 ))
112 } else if call.method == "printingInfo" { 112 } else if call.method == "printingInfo" {
113 result(PrintJob.printingInfo()) 113 result(PrintJob.printingInfo())
  114 + } else if call.method == "rasterPdf" {
  115 + let doc = args["doc"] as! FlutterStandardTypedData
  116 + let pages = args["pages"] as? [Int]
  117 + let scale = CGFloat((args["scale"] as! NSNumber).floatValue)
  118 + let printJob = PrintJob(printing: self, index: args["job"] as! Int)
  119 + printJob.rasterPdf(data: doc.data,
  120 + pages: pages,
  121 + scale: scale)
  122 + result(NSNumber(value: 1))
114 } else { 123 } else {
115 result(FlutterMethodNotImplemented) 124 result(FlutterMethodNotImplemented)
116 } 125 }
@@ -165,4 +174,22 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -165,4 +174,22 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
165 ] 174 ]
166 channel.invokeMethod("onHtmlError", arguments: data) 175 channel.invokeMethod("onHtmlError", arguments: data)
167 } 176 }
  177 +
  178 + /// send pdf to raster data result to flutter
  179 + public func onPageRasterized(printJob: PrintJob, imageData: Data, width: Int, height: Int) {
  180 + let data: NSDictionary = [
  181 + "image": FlutterStandardTypedData(bytes: imageData),
  182 + "width": width,
  183 + "height": height,
  184 + "job": printJob.index,
  185 + ]
  186 + channel.invokeMethod("onPageRasterized", arguments: data)
  187 + }
  188 +
  189 + public func onPageRasterEnd(printJob: PrintJob) {
  190 + let data: NSDictionary = [
  191 + "job": printJob.index,
  192 + ]
  193 + channel.invokeMethod("onPageRasterEnd", arguments: data)
  194 + }
168 } 195 }
@@ -31,4 +31,5 @@ part 'src/print_job.dart'; @@ -31,4 +31,5 @@ part 'src/print_job.dart';
31 part 'src/printer.dart'; 31 part 'src/printer.dart';
32 part 'src/printing.dart'; 32 part 'src/printing.dart';
33 part 'src/printing_info.dart'; 33 part 'src/printing_info.dart';
  34 +part 'src/raster.dart';
34 part 'src/widgets.dart'; 35 part 'src/widgets.dart';
@@ -21,11 +21,13 @@ class _PrintJob { @@ -21,11 +21,13 @@ class _PrintJob {
21 this.onLayout, 21 this.onLayout,
22 this.onHtmlRendered, 22 this.onHtmlRendered,
23 this.onCompleted, 23 this.onCompleted,
  24 + this.onPageRasterized,
24 }); 25 });
25 26
26 final LayoutCallback onLayout; 27 final LayoutCallback onLayout;
27 final Completer<List<int>> onHtmlRendered; 28 final Completer<List<int>> onHtmlRendered;
28 final Completer<bool> onCompleted; 29 final Completer<bool> onCompleted;
  30 + final StreamController<PdfRaster> onPageRasterized;
29 31
30 int index; 32 int index;
31 } 33 }
@@ -68,6 +68,20 @@ mixin Printing { @@ -68,6 +68,20 @@ mixin Printing {
68 final _PrintJob job = _printJobs[call.arguments['job']]; 68 final _PrintJob job = _printJobs[call.arguments['job']];
69 job.onHtmlRendered.completeError(call.arguments['error']); 69 job.onHtmlRendered.completeError(call.arguments['error']);
70 break; 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;
71 } 85 }
72 } 86 }
73 87
@@ -273,4 +287,26 @@ mixin Printing { @@ -273,4 +287,26 @@ mixin Printing {
273 287
274 return PrintingInfo.fromMap(result); 288 return PrintingInfo.fromMap(result);
275 } 289 }
  290 +
  291 + static Stream<PdfRaster> raster(
  292 + List<int> document, {
  293 + List<int> pages,
  294 + double dpi = PdfPageFormat.inch,
  295 + }) {
  296 + _channel.setMethodCallHandler(_handleMethod);
  297 +
  298 + final _PrintJob job = _newPrintJob(_PrintJob(
  299 + onPageRasterized: StreamController<PdfRaster>(),
  300 + ));
  301 +
  302 + final Map<String, dynamic> params = <String, dynamic>{
  303 + 'doc': Uint8List.fromList(document),
  304 + 'pages': pages,
  305 + 'scale': dpi / PdfPageFormat.inch,
  306 + 'job': job.index,
  307 + };
  308 +
  309 + _channel.invokeMethod<void>('rasterPdf', params);
  310 + return job.onPageRasterized.stream;
  311 + }
276 } 312 }
@@ -23,6 +23,7 @@ class PrintingInfo { @@ -23,6 +23,7 @@ class PrintingInfo {
23 canPrint: map['canPrint'], 23 canPrint: map['canPrint'],
24 canConvertHtml: map['canConvertHtml'], 24 canConvertHtml: map['canConvertHtml'],
25 canShare: map['canShare'], 25 canShare: map['canShare'],
  26 + canRaster: map['canRaster'],
26 ); 27 );
27 28
28 const PrintingInfo._({ 29 const PrintingInfo._({
@@ -31,11 +32,13 @@ class PrintingInfo { @@ -31,11 +32,13 @@ class PrintingInfo {
31 this.canPrint = false, 32 this.canPrint = false,
32 this.canConvertHtml = false, 33 this.canConvertHtml = false,
33 this.canShare = false, 34 this.canShare = false,
  35 + this.canRaster = false,
34 }) : assert(directPrint != null), 36 }) : assert(directPrint != null),
35 assert(dynamicLayout != null), 37 assert(dynamicLayout != null),
36 assert(canPrint != null), 38 assert(canPrint != null),
37 assert(canConvertHtml != null), 39 assert(canConvertHtml != null),
38 - assert(canShare != null); 40 + assert(canShare != null),
  41 + assert(canRaster != null);
39 42
40 static const PrintingInfo unavailable = PrintingInfo._(); 43 static const PrintingInfo unavailable = PrintingInfo._();
41 44
@@ -44,4 +47,5 @@ class PrintingInfo { @@ -44,4 +47,5 @@ class PrintingInfo {
44 final bool canPrint; 47 final bool canPrint;
45 final bool canConvertHtml; 48 final bool canConvertHtml;
46 final bool canShare; 49 final bool canShare;
  50 + final bool canRaster;
47 } 51 }
  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 +class PdfRaster {
  20 + PdfRaster._(
  21 + this.width,
  22 + this.height,
  23 + this.pixels,
  24 + );
  25 +
  26 + final int width;
  27 + final int height;
  28 + final Uint8List pixels;
  29 +
  30 + @override
  31 + String toString() => 'Image ${width}x$height ${pixels.lengthInBytes} bytes';
  32 +
  33 + /// Decode RGBA raw image to dart:ui Image
  34 + Future<ui.Image> toImage() {
  35 + final Completer<ui.Image> comp = Completer<ui.Image>();
  36 + ui.decodeImageFromPixels(
  37 + pixels,
  38 + width,
  39 + height,
  40 + ui.PixelFormat.rgba8888,
  41 + (ui.Image image) => comp.complete(image),
  42 + );
  43 + return comp.future;
  44 + }
  45 +
  46 + /// Convert to a PNG image
  47 + Future<Uint8List> toPng() async {
  48 + final ui.Image image = await toImage();
  49 + final ByteData data =
  50 + await image.toByteData(format: ui.ImageByteFormat.png);
  51 + return data.buffer.asUint8List();
  52 + }
  53 +}
  54 +
  55 +class PdfRasterImage extends ImageProvider<PdfRaster> {
  56 + PdfRasterImage(this.raster);
  57 +
  58 + final PdfRaster raster;
  59 +
  60 + Future<ImageInfo> _loadAsync() async {
  61 + final ui.Image uiImage = await raster.toImage();
  62 + return ImageInfo(image: uiImage, scale: 1);
  63 + }
  64 +
  65 + @override
  66 + ImageStreamCompleter load(PdfRaster key, DecoderCallback decode) {
  67 + return OneFrameImageStreamCompleter(_loadAsync());
  68 + }
  69 +
  70 + @override
  71 + Future<PdfRaster> obtainKey(ImageConfiguration configuration) async {
  72 + return raster;
  73 + }
  74 +}
@@ -168,6 +168,41 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -168,6 +168,41 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
168 result(NSNumber(value: 1)) 168 result(NSNumber(value: 1))
169 } 169 }
170 170
  171 + public func rasterPdf(data: Data, pages: [Int]?, scale: CGFloat) {
  172 + let provider = CGDataProvider(data: data as CFData)!
  173 + let document = CGPDFDocument(provider)!
  174 +
  175 + DispatchQueue.global().async {
  176 + let pageCount = document.numberOfPages
  177 +
  178 + for pageNum in pages ?? Array(0 ... pageCount - 1) {
  179 + guard let page = document.page(at: pageNum + 1) else { continue }
  180 + let rect = page.getBoxRect(.mediaBox)
  181 + let width = Int(rect.width * scale)
  182 + let height = Int(rect.height * scale)
  183 + let stride = width * 4
  184 + var data = Data(repeating: 0, count: stride * height)
  185 +
  186 + data.withUnsafeMutableBytes { (ptr: UnsafeMutablePointer<UInt8>) in
  187 + let rgb = CGColorSpaceCreateDeviceRGB()
  188 + let context = CGContext(data: ptr, width: width, height: height, bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
  189 + if context != nil {
  190 + context!.scaleBy(x: scale, y: scale)
  191 + context!.drawPDFPage(page)
  192 + }
  193 + }
  194 +
  195 + DispatchQueue.main.sync {
  196 + self.printing.onPageRasterized(printJob: self, imageData: data, width: width, height: height)
  197 + }
  198 + }
  199 +
  200 + DispatchQueue.main.sync {
  201 + self.printing.onPageRasterEnd(printJob: self)
  202 + }
  203 + }
  204 + }
  205 +
171 public static func printingInfo() -> NSDictionary { 206 public static func printingInfo() -> NSDictionary {
172 let data: NSDictionary = [ 207 let data: NSDictionary = [
173 "directPrint": false, 208 "directPrint": false,
@@ -175,6 +210,7 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -175,6 +210,7 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
175 "canPrint": true, 210 "canPrint": true,
176 "canConvertHtml": true, 211 "canConvertHtml": true,
177 "canShare": true, 212 "canShare": true,
  213 + "canRaster": true,
178 ] 214 ]
179 return data 215 return data
180 } 216 }
@@ -111,6 +111,15 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -111,6 +111,15 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
111 // )) 111 // ))
112 } else if call.method == "printingInfo" { 112 } else if call.method == "printingInfo" {
113 result(PrintJob.printingInfo()) 113 result(PrintJob.printingInfo())
  114 + } else if call.method == "rasterPdf" {
  115 + let doc = args["doc"] as! FlutterStandardTypedData
  116 + let pages = args["pages"] as? [Int]
  117 + let scale = CGFloat((args["scale"] as! NSNumber).floatValue)
  118 + let printJob = PrintJob(printing: self, index: args["job"] as! Int)
  119 + printJob.rasterPdf(data: doc.data,
  120 + pages: pages,
  121 + scale: scale)
  122 + result(NSNumber(value: 1))
114 } else { 123 } else {
115 result(FlutterMethodNotImplemented) 124 result(FlutterMethodNotImplemented)
116 } 125 }
@@ -165,4 +174,22 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -165,4 +174,22 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
165 ] 174 ]
166 channel.invokeMethod("onHtmlError", arguments: data) 175 channel.invokeMethod("onHtmlError", arguments: data)
167 } 176 }
  177 +
  178 + /// send pdf to raster data result to flutter
  179 + public func onPageRasterized(printJob: PrintJob, imageData: Data, width: Int, height: Int) {
  180 + let data: NSDictionary = [
  181 + "image": FlutterStandardTypedData(bytes: imageData),
  182 + "width": width,
  183 + "height": height,
  184 + "job": printJob.index,
  185 + ]
  186 + channel.invokeMethod("onPageRasterized", arguments: data)
  187 + }
  188 +
  189 + public func onPageRasterEnd(printJob: PrintJob) {
  190 + let data: NSDictionary = [
  191 + "job": printJob.index,
  192 + ]
  193 + channel.invokeMethod("onPageRasterEnd", arguments: data)
  194 + }
168 } 195 }
@@ -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.0.1 7 +version: 3.0.2
8 8
9 environment: 9 environment:
10 sdk: ">=2.3.0 <3.0.0" 10 sdk: ">=2.3.0 <3.0.0"