Showing
8 changed files
with
307 additions
and
112 deletions
| 1 | # 1.2.0 | 1 | # 1.2.0 |
| 2 | * Fix compileSdkVersion to match appcompat | 2 | * Fix compileSdkVersion to match appcompat |
| 3 | * Change license to Apache 2.0 | 3 | * Change license to Apache 2.0 |
| 4 | +* Implement asynchronous printing driven by the OS | ||
| 4 | 5 | ||
| 5 | # 1.1.0 | 6 | # 1.1.0 |
| 6 | * Rename classes to satisfy Dart conventions | 7 | * Rename classes to satisfy Dart conventions |
| @@ -16,6 +16,8 @@ | @@ -16,6 +16,8 @@ | ||
| 16 | 16 | ||
| 17 | package net.nfet.flutter.printing; | 17 | package net.nfet.flutter.printing; |
| 18 | 18 | ||
| 19 | +import static java.lang.StrictMath.max; | ||
| 20 | + | ||
| 19 | import android.app.Activity; | 21 | import android.app.Activity; |
| 20 | import android.content.Context; | 22 | import android.content.Context; |
| 21 | import android.content.Intent; | 23 | import android.content.Intent; |
| @@ -36,6 +38,7 @@ import java.io.File; | @@ -36,6 +38,7 @@ import java.io.File; | ||
| 36 | import java.io.FileOutputStream; | 38 | import java.io.FileOutputStream; |
| 37 | import java.io.IOException; | 39 | import java.io.IOException; |
| 38 | import java.io.OutputStream; | 40 | import java.io.OutputStream; |
| 41 | +import java.util.HashMap; | ||
| 39 | 42 | ||
| 40 | import io.flutter.plugin.common.MethodCall; | 43 | import io.flutter.plugin.common.MethodCall; |
| 41 | import io.flutter.plugin.common.MethodChannel; | 44 | import io.flutter.plugin.common.MethodChannel; |
| @@ -46,12 +49,18 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; | @@ -46,12 +49,18 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; | ||
| 46 | /** | 49 | /** |
| 47 | * PrintingPlugin | 50 | * PrintingPlugin |
| 48 | */ | 51 | */ |
| 49 | -public class PrintingPlugin implements MethodCallHandler { | 52 | +public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHandler { |
| 50 | private static PrintManager printManager; | 53 | private static PrintManager printManager; |
| 51 | private final Activity activity; | 54 | private final Activity activity; |
| 55 | + private final MethodChannel channel; | ||
| 56 | + private PrintedPdfDocument mPdfDocument; | ||
| 57 | + private byte[] documentData; | ||
| 58 | + private String jobName; | ||
| 59 | + private LayoutResultCallback callback; | ||
| 52 | 60 | ||
| 53 | - private PrintingPlugin(Activity activity) { | 61 | + private PrintingPlugin(Activity activity, MethodChannel channel) { |
| 54 | this.activity = activity; | 62 | this.activity = activity; |
| 63 | + this.channel = channel; | ||
| 55 | } | 64 | } |
| 56 | 65 | ||
| 57 | /** | 66 | /** |
| @@ -59,82 +68,97 @@ public class PrintingPlugin implements MethodCallHandler { | @@ -59,82 +68,97 @@ public class PrintingPlugin implements MethodCallHandler { | ||
| 59 | */ | 68 | */ |
| 60 | public static void registerWith(Registrar registrar) { | 69 | public static void registerWith(Registrar registrar) { |
| 61 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "printing"); | 70 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "printing"); |
| 62 | - channel.setMethodCallHandler(new PrintingPlugin(registrar.activity())); | 71 | + channel.setMethodCallHandler(new PrintingPlugin(registrar.activity(), channel)); |
| 63 | printManager = (PrintManager) registrar.activity().getSystemService(Context.PRINT_SERVICE); | 72 | printManager = (PrintManager) registrar.activity().getSystemService(Context.PRINT_SERVICE); |
| 64 | } | 73 | } |
| 65 | 74 | ||
| 66 | @Override | 75 | @Override |
| 76 | + public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor parcelFileDescriptor, | ||
| 77 | + CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { | ||
| 78 | + OutputStream output = null; | ||
| 79 | + try { | ||
| 80 | + output = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); | ||
| 81 | + output.write(documentData, 0, documentData.length); | ||
| 82 | + writeResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES}); | ||
| 83 | + } catch (IOException e) { | ||
| 84 | + e.printStackTrace(); | ||
| 85 | + } finally { | ||
| 86 | + try { | ||
| 87 | + if (output != null) { | ||
| 88 | + output.close(); | ||
| 89 | + } | ||
| 90 | + } catch (IOException e) { | ||
| 91 | + e.printStackTrace(); | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + @Override | ||
| 97 | + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, | ||
| 98 | + CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { | ||
| 99 | + // Create a new PdfDocument with the requested page attributes | ||
| 100 | + mPdfDocument = new PrintedPdfDocument(activity, newAttributes); | ||
| 101 | + | ||
| 102 | + // Respond to cancellation request | ||
| 103 | + if (cancellationSignal.isCanceled()) { | ||
| 104 | + callback.onLayoutCancelled(); | ||
| 105 | + return; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + this.callback = callback; | ||
| 109 | + | ||
| 110 | + HashMap<String, Double> args = new HashMap<>(); | ||
| 111 | + | ||
| 112 | + PrintAttributes.MediaSize size = newAttributes.getMediaSize(); | ||
| 113 | + args.put("width", size.getWidthMils() * 72.0 / 1000.0); | ||
| 114 | + args.put("height", size.getHeightMils() * 72.0 / 1000.0); | ||
| 115 | + | ||
| 116 | + PrintAttributes.Margins margins = newAttributes.getMinMargins(); | ||
| 117 | + args.put("marginLeft", margins.getLeftMils() * 72.0 / 1000.0); | ||
| 118 | + args.put("marginTop", margins.getTopMils() * 72.0 / 1000.0); | ||
| 119 | + args.put("marginRight", margins.getRightMils() * 72.0 / 1000.0); | ||
| 120 | + args.put("marginBottom", margins.getBottomMils() * 72.0 / 1000.0); | ||
| 121 | + | ||
| 122 | + channel.invokeMethod("onLayout", args); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + @Override | ||
| 126 | + public void onFinish() { | ||
| 127 | + // noinspection ResultOfMethodCallIgnored | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + @Override | ||
| 67 | public void onMethodCall(MethodCall call, Result result) { | 131 | public void onMethodCall(MethodCall call, Result result) { |
| 68 | switch (call.method) { | 132 | switch (call.method) { |
| 69 | case "printPdf": | 133 | case "printPdf": |
| 70 | - printPdf((byte[]) call.argument("doc")); | 134 | + jobName = |
| 135 | + call.argument("name") == null ? "Document" : (String) call.argument("name"); | ||
| 136 | + assert jobName != null; | ||
| 137 | + printManager.print(jobName, this, null); | ||
| 71 | result.success(0); | 138 | result.success(0); |
| 72 | break; | 139 | break; |
| 73 | - case "sharePdf": | ||
| 74 | - sharePdf((byte[]) call.argument("doc")); | ||
| 75 | - result.success(0); | ||
| 76 | - break; | ||
| 77 | - default: | ||
| 78 | - result.notImplemented(); | ||
| 79 | - break; | ||
| 80 | - } | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | - private void printPdf(final byte[] documentData) { | ||
| 84 | - PrintDocumentAdapter pda = new PrintDocumentAdapter() { | ||
| 85 | - PrintedPdfDocument mPdfDocument; | ||
| 86 | - | ||
| 87 | - @Override | ||
| 88 | - public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor parcelFileDescriptor, | ||
| 89 | - CancellationSignal cancellationSignal, | ||
| 90 | - WriteResultCallback writeResultCallback) { | ||
| 91 | - OutputStream output = null; | ||
| 92 | - try { | ||
| 93 | - output = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); | ||
| 94 | - output.write(documentData, 0, documentData.length); | ||
| 95 | - writeResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES}); | ||
| 96 | - } catch (IOException e) { | ||
| 97 | - e.printStackTrace(); | ||
| 98 | - } finally { | ||
| 99 | - try { | ||
| 100 | - if (output != null) { | ||
| 101 | - output.close(); | ||
| 102 | - } | ||
| 103 | - } catch (IOException e) { | ||
| 104 | - e.printStackTrace(); | ||
| 105 | - } | ||
| 106 | - } | ||
| 107 | - } | ||
| 108 | - | ||
| 109 | - @Override | ||
| 110 | - public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, | ||
| 111 | - CancellationSignal cancellationSignal, LayoutResultCallback callback, | ||
| 112 | - Bundle extras) { | ||
| 113 | - // Create a new PdfDocument with the requested page attributes | ||
| 114 | - mPdfDocument = new PrintedPdfDocument(activity, newAttributes); | ||
| 115 | - | ||
| 116 | - // Respond to cancellation request | ||
| 117 | - if (cancellationSignal.isCanceled()) { | ||
| 118 | - callback.onLayoutCancelled(); | ||
| 119 | - return; | ||
| 120 | - } | 140 | + case "writePdf": |
| 141 | + documentData = (byte[]) call.argument("doc"); | ||
| 121 | 142 | ||
| 122 | // Return print information to print framework | 143 | // Return print information to print framework |
| 123 | PrintDocumentInfo info = | 144 | PrintDocumentInfo info = |
| 124 | - new PrintDocumentInfo.Builder("document.pdf") | 145 | + new PrintDocumentInfo.Builder(jobName + ".pdf") |
| 125 | .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) | 146 | .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) |
| 126 | .build(); | 147 | .build(); |
| 148 | + | ||
| 127 | // Content layout reflow is complete | 149 | // Content layout reflow is complete |
| 128 | callback.onLayoutFinished(info, true); | 150 | callback.onLayoutFinished(info, true); |
| 129 | - } | ||
| 130 | 151 | ||
| 131 | - @Override | ||
| 132 | - public void onFinish() { | ||
| 133 | - // noinspection ResultOfMethodCallIgnored | ||
| 134 | - } | ||
| 135 | - }; | ||
| 136 | - String jobName = "Document"; | ||
| 137 | - printManager.print(jobName, pda, null); | 152 | + result.success(0); |
| 153 | + break; | ||
| 154 | + case "sharePdf": | ||
| 155 | + sharePdf((byte[]) call.argument("doc")); | ||
| 156 | + result.success(0); | ||
| 157 | + break; | ||
| 158 | + default: | ||
| 159 | + result.notImplemented(); | ||
| 160 | + break; | ||
| 161 | + } | ||
| 138 | } | 162 | } |
| 139 | 163 | ||
| 140 | private void sharePdf(byte[] data) { | 164 | private void sharePdf(byte[] data) { |
| @@ -25,8 +25,9 @@ class MyAppState extends State<MyApp> { | @@ -25,8 +25,9 @@ class MyAppState extends State<MyApp> { | ||
| 25 | 25 | ||
| 26 | void _printPdf() async { | 26 | void _printPdf() async { |
| 27 | print("Print ..."); | 27 | print("Print ..."); |
| 28 | - final pdf = await generateDocument(PdfPageFormat.a4); | ||
| 29 | - Printing.printPdf(document: pdf); | 28 | + await Printing.layoutPdf( |
| 29 | + onLayout: (PdfPageFormat format) async => | ||
| 30 | + (await generateDocument(format)).save()); | ||
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | void _sharePdf() async { | 33 | void _sharePdf() async { |
| @@ -46,45 +47,49 @@ class MyAppState extends State<MyApp> { | @@ -46,45 +47,49 @@ class MyAppState extends State<MyApp> { | ||
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | Future<void> _printScreen() async { | 49 | Future<void> _printScreen() async { |
| 49 | - final pdf = PdfDocument(deflate: zlib.encode); | ||
| 50 | - final page = PdfPage(pdf, pageFormat: PdfPageFormat.a4); | ||
| 51 | - final g = page.getGraphics(); | ||
| 52 | - | ||
| 53 | RenderRepaintBoundary boundary = | 50 | RenderRepaintBoundary boundary = |
| 54 | previewContainer.currentContext.findRenderObject(); | 51 | previewContainer.currentContext.findRenderObject(); |
| 55 | final im = await boundary.toImage(); | 52 | final im = await boundary.toImage(); |
| 56 | final bytes = await im.toByteData(format: ImageByteFormat.rawRgba); | 53 | final bytes = await im.toByteData(format: ImageByteFormat.rawRgba); |
| 57 | print("Print Screen ${im.width}x${im.height} ..."); | 54 | print("Print Screen ${im.width}x${im.height} ..."); |
| 58 | 55 | ||
| 59 | - // Center the image | ||
| 60 | - final w = page.pageFormat.width - | ||
| 61 | - page.pageFormat.marginLeft - | ||
| 62 | - page.pageFormat.marginRight; | ||
| 63 | - final h = page.pageFormat.height - | ||
| 64 | - page.pageFormat.marginTop - | ||
| 65 | - page.pageFormat.marginBottom; | ||
| 66 | - double iw, ih; | ||
| 67 | - if (im.width.toDouble() / im.height.toDouble() < 1.0) { | ||
| 68 | - ih = h; | ||
| 69 | - iw = im.width.toDouble() * ih / im.height.toDouble(); | ||
| 70 | - } else { | ||
| 71 | - iw = w; | ||
| 72 | - ih = im.height.toDouble() * iw / im.width.toDouble(); | ||
| 73 | - } | ||
| 74 | - | ||
| 75 | - PdfImage image = PdfImage(pdf, | ||
| 76 | - image: bytes.buffer.asUint8List(), width: im.width, height: im.height); | ||
| 77 | - g.drawImage( | ||
| 78 | - image, | ||
| 79 | - page.pageFormat.marginLeft + (w - iw) / 2.0, | ||
| 80 | - page.pageFormat.height - | ||
| 81 | - page.pageFormat.marginTop - | ||
| 82 | - ih - | ||
| 83 | - (h - ih) / 2.0, | ||
| 84 | - iw, | ||
| 85 | - ih); | ||
| 86 | - | ||
| 87 | - Printing.printPdf(document: pdf); | 56 | + Printing.layoutPdf(onLayout: (PdfPageFormat format) { |
| 57 | + final pdf = PdfDocument(deflate: zlib.encode); | ||
| 58 | + final page = PdfPage(pdf, pageFormat: format); | ||
| 59 | + final g = page.getGraphics(); | ||
| 60 | + | ||
| 61 | + // Center the image | ||
| 62 | + final w = page.pageFormat.width - | ||
| 63 | + page.pageFormat.marginLeft - | ||
| 64 | + page.pageFormat.marginRight; | ||
| 65 | + final h = page.pageFormat.height - | ||
| 66 | + page.pageFormat.marginTop - | ||
| 67 | + page.pageFormat.marginBottom; | ||
| 68 | + double iw, ih; | ||
| 69 | + if (im.width.toDouble() / im.height.toDouble() < 1.0) { | ||
| 70 | + ih = h; | ||
| 71 | + iw = im.width.toDouble() * ih / im.height.toDouble(); | ||
| 72 | + } else { | ||
| 73 | + iw = w; | ||
| 74 | + ih = im.height.toDouble() * iw / im.width.toDouble(); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + PdfImage image = PdfImage(pdf, | ||
| 78 | + image: bytes.buffer.asUint8List(), | ||
| 79 | + width: im.width, | ||
| 80 | + height: im.height); | ||
| 81 | + g.drawImage( | ||
| 82 | + image, | ||
| 83 | + page.pageFormat.marginLeft + (w - iw) / 2.0, | ||
| 84 | + page.pageFormat.height - | ||
| 85 | + page.pageFormat.marginTop - | ||
| 86 | + ih - | ||
| 87 | + (h - ih) / 2.0, | ||
| 88 | + iw, | ||
| 89 | + ih); | ||
| 90 | + | ||
| 91 | + return pdf.save(); | ||
| 92 | + }); | ||
| 88 | } | 93 | } |
| 89 | 94 | ||
| 90 | @override | 95 | @override |
printing/ios/Classes/PageRenderer.h
0 → 100644
| 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 <Flutter/Flutter.h> | ||
| 18 | + | ||
| 19 | +@interface PdfPrintPageRenderer : UIPrintPageRenderer | ||
| 20 | + | ||
| 21 | +- (instancetype)init:(FlutterMethodChannel*)channel; | ||
| 22 | +- (void)drawPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)printableRect; | ||
| 23 | + | ||
| 24 | +@property(nonatomic, readonly) NSInteger numberOfPages; | ||
| 25 | +@property(nonatomic, readonly) NSLock* lock; | ||
| 26 | +@property(nonatomic) CGPDFDocumentRef pdfDocument; | ||
| 27 | +@end |
printing/ios/Classes/PageRenderer.m
0 → 100644
| 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 "PageRenderer.h" | ||
| 18 | + | ||
| 19 | +@implementation PdfPrintPageRenderer { | ||
| 20 | + FlutterMethodChannel* channel; | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +@synthesize lock; | ||
| 24 | +@synthesize pdfDocument; | ||
| 25 | + | ||
| 26 | +- (instancetype)init:(FlutterMethodChannel*)channel { | ||
| 27 | + self = [super init]; | ||
| 28 | + self->channel = channel; | ||
| 29 | + self->lock = [[NSLock alloc] init]; | ||
| 30 | + self->pdfDocument = nil; | ||
| 31 | + return self; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +- (NSInteger)numberOfPages { | ||
| 35 | + NSNumber* width = [[NSNumber alloc] initWithDouble:self.paperRect.size.width]; | ||
| 36 | + NSNumber* height = | ||
| 37 | + [[NSNumber alloc] initWithDouble:self.paperRect.size.height]; | ||
| 38 | + NSNumber* marginLeft = | ||
| 39 | + [[NSNumber alloc] initWithDouble:self.printableRect.origin.x]; | ||
| 40 | + NSNumber* marginTop = | ||
| 41 | + [[NSNumber alloc] initWithDouble:self.printableRect.origin.y]; | ||
| 42 | + NSNumber* marginRight = | ||
| 43 | + [[NSNumber alloc] initWithDouble:self.paperRect.size.width - | ||
| 44 | + (self.printableRect.origin.x + | ||
| 45 | + self.printableRect.size.width)]; | ||
| 46 | + NSNumber* marginBottom = | ||
| 47 | + [[NSNumber alloc] initWithDouble:self.paperRect.size.height - | ||
| 48 | + (self.printableRect.origin.y + | ||
| 49 | + self.printableRect.size.height)]; | ||
| 50 | + | ||
| 51 | + NSDictionary* arg = @{ | ||
| 52 | + @"width" : width, | ||
| 53 | + @"height" : height, | ||
| 54 | + @"marginLeft" : marginLeft, | ||
| 55 | + @"marginTop" : marginTop, | ||
| 56 | + @"marginRight" : marginRight, | ||
| 57 | + @"marginBottom" : marginBottom, | ||
| 58 | + }; | ||
| 59 | + | ||
| 60 | + [lock lock]; | ||
| 61 | + [channel invokeMethod:@"onLayout" arguments:arg]; | ||
| 62 | + [lock lock]; | ||
| 63 | + [lock unlock]; | ||
| 64 | + | ||
| 65 | + size_t pages = CGPDFDocumentGetNumberOfPages(pdfDocument); | ||
| 66 | + | ||
| 67 | + return pages; | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +- (void)drawPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)printableRect { | ||
| 71 | + CGContextRef ctx = UIGraphicsGetCurrentContext(); | ||
| 72 | + CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, pageIndex + 1); | ||
| 73 | + CGContextScaleCTM(ctx, 1.0, -1.0); | ||
| 74 | + CGContextTranslateCTM(ctx, 0.0, -self.paperRect.size.height); | ||
| 75 | + CGContextDrawPDFPage(ctx, page); | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +@end |
| @@ -18,4 +18,6 @@ | @@ -18,4 +18,6 @@ | ||
| 18 | 18 | ||
| 19 | @interface PrintingPlugin | 19 | @interface PrintingPlugin |
| 20 | : NSObject <FlutterPlugin, UIPrintInteractionControllerDelegate> | 20 | : NSObject <FlutterPlugin, UIPrintInteractionControllerDelegate> |
| 21 | + | ||
| 22 | +- (instancetype)init:(FlutterMethodChannel*)channel; | ||
| 21 | @end | 23 | @end |
| @@ -15,19 +15,34 @@ | @@ -15,19 +15,34 @@ | ||
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | #import "PrintingPlugin.h" | 17 | #import "PrintingPlugin.h" |
| 18 | +#import "PageRenderer.h" | ||
| 19 | + | ||
| 20 | +@implementation PrintingPlugin { | ||
| 21 | + FlutterMethodChannel* channel; | ||
| 22 | + PdfPrintPageRenderer* renderer; | ||
| 23 | +} | ||
| 18 | 24 | ||
| 19 | -@implementation PrintingPlugin | ||
| 20 | + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { | 25 | + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { |
| 21 | FlutterMethodChannel* channel = | 26 | FlutterMethodChannel* channel = |
| 22 | [FlutterMethodChannel methodChannelWithName:@"printing" | 27 | [FlutterMethodChannel methodChannelWithName:@"printing" |
| 23 | binaryMessenger:[registrar messenger]]; | 28 | binaryMessenger:[registrar messenger]]; |
| 24 | - PrintingPlugin* instance = [[PrintingPlugin alloc] init]; | 29 | + PrintingPlugin* instance = [[PrintingPlugin alloc] init:channel]; |
| 25 | [registrar addMethodCallDelegate:instance channel:channel]; | 30 | [registrar addMethodCallDelegate:instance channel:channel]; |
| 26 | } | 31 | } |
| 27 | 32 | ||
| 33 | +- (instancetype)init:(FlutterMethodChannel*)channel { | ||
| 34 | + self = [super init]; | ||
| 35 | + self->channel = channel; | ||
| 36 | + self->renderer = [[PdfPrintPageRenderer alloc] init:channel]; | ||
| 37 | + return self; | ||
| 38 | +} | ||
| 39 | + | ||
| 28 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { | 40 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { |
| 29 | if ([@"printPdf" isEqualToString:call.method]) { | 41 | if ([@"printPdf" isEqualToString:call.method]) { |
| 30 | - [self printPdf:[call.arguments objectForKey:@"doc"]]; | 42 | + [self printPdf:[call.arguments objectForKey:@"name"]]; |
| 43 | + result(@1); | ||
| 44 | + } else if ([@"writePdf" isEqualToString:call.method]) { | ||
| 45 | + [self writePdf:[call.arguments objectForKey:@"doc"]]; | ||
| 31 | result(@1); | 46 | result(@1); |
| 32 | } else if ([@"sharePdf" isEqualToString:call.method]) { | 47 | } else if ([@"sharePdf" isEqualToString:call.method]) { |
| 33 | [self sharePdf:[call.arguments objectForKey:@"doc"] | 48 | [self sharePdf:[call.arguments objectForKey:@"doc"] |
| @@ -42,22 +57,22 @@ | @@ -42,22 +57,22 @@ | ||
| 42 | } | 57 | } |
| 43 | } | 58 | } |
| 44 | 59 | ||
| 45 | -- (void)printPdf:(nonnull FlutterStandardTypedData*)data { | 60 | +- (void)printPdf:(nonnull NSString*)name { |
| 46 | BOOL printing = [UIPrintInteractionController isPrintingAvailable]; | 61 | BOOL printing = [UIPrintInteractionController isPrintingAvailable]; |
| 47 | if (!printing) { | 62 | if (!printing) { |
| 48 | NSLog(@"printing not available"); | 63 | NSLog(@"printing not available"); |
| 49 | return; | 64 | return; |
| 50 | } | 65 | } |
| 51 | 66 | ||
| 52 | - BOOL dataOK = [UIPrintInteractionController canPrintData:[data data]]; | ||
| 53 | - if (!dataOK) | ||
| 54 | - NSLog(@"data not ok to be printed"); | ||
| 55 | - | ||
| 56 | UIPrintInteractionController* controller = | 67 | UIPrintInteractionController* controller = |
| 57 | [UIPrintInteractionController sharedPrintController]; | 68 | [UIPrintInteractionController sharedPrintController]; |
| 58 | [controller setDelegate:self]; | 69 | [controller setDelegate:self]; |
| 59 | - [controller setPrintingItem:[data data]]; | ||
| 60 | 70 | ||
| 71 | + UIPrintInfo* printInfo = [UIPrintInfo printInfo]; | ||
| 72 | + printInfo.jobName = name; | ||
| 73 | + printInfo.outputType = UIPrintInfoOutputGeneral; | ||
| 74 | + controller.printInfo = printInfo; | ||
| 75 | + [controller setPrintPageRenderer:renderer]; | ||
| 61 | UIPrintInteractionCompletionHandler completionHandler = | 76 | UIPrintInteractionCompletionHandler completionHandler = |
| 62 | ^(UIPrintInteractionController* printController, BOOL completed, | 77 | ^(UIPrintInteractionController* printController, BOOL completed, |
| 63 | NSError* error) { | 78 | NSError* error) { |
| @@ -65,11 +80,27 @@ | @@ -65,11 +80,27 @@ | ||
| 65 | NSLog(@"FAILED! due to error in domain %@ with error code %u", | 80 | NSLog(@"FAILED! due to error in domain %@ with error code %u", |
| 66 | error.domain, (unsigned int)error.code); | 81 | error.domain, (unsigned int)error.code); |
| 67 | } | 82 | } |
| 83 | + if (self->renderer.pdfDocument != nil) { | ||
| 84 | + CGPDFDocumentRelease(self->renderer.pdfDocument); | ||
| 85 | + self->renderer.pdfDocument = nil; | ||
| 86 | + } | ||
| 68 | }; | 87 | }; |
| 69 | 88 | ||
| 70 | [controller presentAnimated:YES completionHandler:completionHandler]; | 89 | [controller presentAnimated:YES completionHandler:completionHandler]; |
| 71 | } | 90 | } |
| 72 | 91 | ||
| 92 | +- (void)writePdf:(nonnull FlutterStandardTypedData*)data { | ||
| 93 | + CGDataProviderRef dataProvider = CGDataProviderCreateWithData( | ||
| 94 | + NULL, data.data.bytes, data.data.length, NULL); | ||
| 95 | + if (renderer.pdfDocument != nil) { | ||
| 96 | + CGPDFDocumentRelease(renderer.pdfDocument); | ||
| 97 | + renderer.pdfDocument = nil; | ||
| 98 | + } | ||
| 99 | + renderer.pdfDocument = CGPDFDocumentCreateWithProvider(dataProvider); | ||
| 100 | + CGDataProviderRelease(dataProvider); | ||
| 101 | + [renderer.lock unlock]; | ||
| 102 | +} | ||
| 103 | + | ||
| 73 | - (void)sharePdf:(nonnull FlutterStandardTypedData*)data | 104 | - (void)sharePdf:(nonnull FlutterStandardTypedData*)data |
| 74 | withSourceRect:(CGRect)rect { | 105 | withSourceRect:(CGRect)rect { |
| 75 | NSURL* tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() | 106 | NSURL* tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() |
| @@ -16,20 +16,47 @@ | @@ -16,20 +16,47 @@ | ||
| 16 | 16 | ||
| 17 | part of printing; | 17 | part of printing; |
| 18 | 18 | ||
| 19 | +typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format); | ||
| 20 | + | ||
| 19 | class Printing { | 21 | class Printing { |
| 20 | static const MethodChannel _channel = MethodChannel('printing'); | 22 | static const MethodChannel _channel = MethodChannel('printing'); |
| 23 | + static LayoutCallback _onLayout; | ||
| 24 | + | ||
| 25 | + static Future<dynamic> _handleMethod(MethodCall call) async { | ||
| 26 | + switch (call.method) { | ||
| 27 | + case "onLayout": | ||
| 28 | + final bytes = await _onLayout(PdfPageFormat( | ||
| 29 | + call.arguments['width'], | ||
| 30 | + call.arguments['height'], | ||
| 31 | + marginLeft: call.arguments['marginLeft'], | ||
| 32 | + marginTop: call.arguments['marginTop'], | ||
| 33 | + marginRight: call.arguments['marginRight'], | ||
| 34 | + marginBottom: call.arguments['marginBottom'], | ||
| 35 | + )); | ||
| 36 | + final Map<String, dynamic> params = <String, dynamic>{ | ||
| 37 | + 'doc': Uint8List.fromList(bytes), | ||
| 38 | + }; | ||
| 39 | + await _channel.invokeMethod('writePdf', params); | ||
| 40 | + return Future.value(""); | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + static Future<Null> layoutPdf( | ||
| 45 | + {@required LayoutCallback onLayout, String name = "Document"}) async { | ||
| 46 | + _onLayout = onLayout; | ||
| 47 | + _channel.setMethodCallHandler(_handleMethod); | ||
| 48 | + final Map<String, dynamic> params = <String, dynamic>{'name': name}; | ||
| 49 | + await _channel.invokeMethod('printPdf', params); | ||
| 50 | + } | ||
| 21 | 51 | ||
| 52 | + @deprecated | ||
| 22 | static Future<Null> printPdf({PdfDocument document, List<int> bytes}) async { | 53 | static Future<Null> printPdf({PdfDocument document, List<int> bytes}) async { |
| 23 | assert(document != null || bytes != null); | 54 | assert(document != null || bytes != null); |
| 24 | assert(!(document == null && bytes == null)); | 55 | assert(!(document == null && bytes == null)); |
| 25 | 56 | ||
| 26 | - if (document != null) bytes = document.save(); | ||
| 27 | - | ||
| 28 | - final Map<String, dynamic> params = <String, dynamic>{ | ||
| 29 | - 'doc': Uint8List.fromList(bytes), | ||
| 30 | - }; | ||
| 31 | - | ||
| 32 | - await _channel.invokeMethod('printPdf', params); | 57 | + layoutPdf( |
| 58 | + onLayout: (PdfPageFormat format) => | ||
| 59 | + document != null ? document.save() : bytes); | ||
| 33 | } | 60 | } |
| 34 | 61 | ||
| 35 | static Future<Null> sharePdf( | 62 | static Future<Null> sharePdf( |
-
Please register or login to post a comment