Showing
16 changed files
with
987 additions
and
664 deletions
@@ -16,9 +16,10 @@ | @@ -16,9 +16,10 @@ | ||
16 | 16 | ||
17 | package android.print; | 17 | package android.print; |
18 | 18 | ||
19 | -import android.app.Activity; | 19 | +import android.content.Context; |
20 | import android.os.CancellationSignal; | 20 | import android.os.CancellationSignal; |
21 | import android.os.ParcelFileDescriptor; | 21 | import android.os.ParcelFileDescriptor; |
22 | +import android.util.Log; | ||
22 | 23 | ||
23 | import java.io.File; | 24 | import java.io.File; |
24 | import java.io.FileInputStream; | 25 | import java.io.FileInputStream; |
@@ -27,17 +28,16 @@ import java.io.IOException; | @@ -27,17 +28,16 @@ import java.io.IOException; | ||
27 | import java.io.InputStream; | 28 | import java.io.InputStream; |
28 | 29 | ||
29 | public class PdfConvert { | 30 | public class PdfConvert { |
30 | - public static void print(final Activity activity, final PrintDocumentAdapter adapter, | 31 | + public static void print(final Context context, final PrintDocumentAdapter adapter, |
31 | final PrintAttributes attributes, final Result result) { | 32 | final PrintAttributes attributes, final Result result) { |
32 | adapter.onLayout(null, attributes, null, new PrintDocumentAdapter.LayoutResultCallback() { | 33 | adapter.onLayout(null, attributes, null, new PrintDocumentAdapter.LayoutResultCallback() { |
33 | @Override | 34 | @Override |
34 | public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { | 35 | public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { |
35 | - File outputDir = activity.getCacheDir(); | ||
36 | - File outputFile = null; | 36 | + File outputDir = context.getCacheDir(); |
37 | + File outputFile; | ||
37 | try { | 38 | try { |
38 | outputFile = File.createTempFile("printing", "pdf", outputDir); | 39 | outputFile = File.createTempFile("printing", "pdf", outputDir); |
39 | } catch (IOException e) { | 40 | } catch (IOException e) { |
40 | - outputFile.delete(); | ||
41 | result.onError(e.getMessage()); | 41 | result.onError(e.getMessage()); |
42 | return; | 42 | return; |
43 | } | 43 | } |
@@ -54,16 +54,22 @@ public class PdfConvert { | @@ -54,16 +54,22 @@ public class PdfConvert { | ||
54 | super.onWriteFinished(pages); | 54 | super.onWriteFinished(pages); |
55 | 55 | ||
56 | if (pages.length == 0) { | 56 | if (pages.length == 0) { |
57 | - finalOutputFile.delete(); | 57 | + if (!finalOutputFile.delete()) { |
58 | + Log.e("PDF", "Unable to delete temporary file"); | ||
59 | + } | ||
58 | result.onError("No page created"); | 60 | result.onError("No page created"); |
59 | } | 61 | } |
60 | 62 | ||
61 | result.onSuccess(finalOutputFile); | 63 | result.onSuccess(finalOutputFile); |
62 | - finalOutputFile.delete(); | 64 | + if (!finalOutputFile.delete()) { |
65 | + Log.e("PDF", "Unable to delete temporary file"); | ||
66 | + } | ||
63 | } | 67 | } |
64 | }); | 68 | }); |
65 | } catch (FileNotFoundException e) { | 69 | } catch (FileNotFoundException e) { |
66 | - outputFile.delete(); | 70 | + if (!outputFile.delete()) { |
71 | + Log.e("PDF", "Unable to delete temporary file"); | ||
72 | + } | ||
67 | result.onError(e.getMessage()); | 73 | result.onError(e.getMessage()); |
68 | } | 74 | } |
69 | } | 75 | } |
@@ -72,17 +78,10 @@ public class PdfConvert { | @@ -72,17 +78,10 @@ public class PdfConvert { | ||
72 | 78 | ||
73 | public static byte[] readFile(File file) throws IOException { | 79 | public static byte[] readFile(File file) throws IOException { |
74 | byte[] buffer = new byte[(int) file.length()]; | 80 | byte[] buffer = new byte[(int) file.length()]; |
75 | - InputStream ios = null; | ||
76 | - try { | ||
77 | - ios = new FileInputStream(file); | 81 | + try (InputStream ios = new FileInputStream(file)) { |
78 | if (ios.read(buffer) == -1) { | 82 | if (ios.read(buffer) == -1) { |
79 | throw new IOException("EOF reached while trying to read the whole file"); | 83 | throw new IOException("EOF reached while trying to read the whole file"); |
80 | } | 84 | } |
81 | - } finally { | ||
82 | - try { | ||
83 | - if (ios != null) ios.close(); | ||
84 | - } catch (IOException e) { | ||
85 | - } | ||
86 | } | 85 | } |
87 | return buffer; | 86 | return buffer; |
88 | } | 87 | } |
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 | +package net.nfet.flutter.printing; | ||
18 | + | ||
19 | +import android.content.Context; | ||
20 | +import android.content.Intent; | ||
21 | +import android.net.Uri; | ||
22 | +import android.os.Build; | ||
23 | +import android.os.Bundle; | ||
24 | +import android.os.CancellationSignal; | ||
25 | +import android.os.Environment; | ||
26 | +import android.os.ParcelFileDescriptor; | ||
27 | +import android.print.PageRange; | ||
28 | +import android.print.PdfConvert; | ||
29 | +import android.print.PrintAttributes; | ||
30 | +import android.print.PrintDocumentAdapter; | ||
31 | +import android.print.PrintDocumentInfo; | ||
32 | +import android.print.PrintJob; | ||
33 | +import android.print.PrintJobInfo; | ||
34 | +import android.print.PrintManager; | ||
35 | +import android.webkit.WebView; | ||
36 | +import android.webkit.WebViewClient; | ||
37 | + | ||
38 | +import androidx.annotation.NonNull; | ||
39 | +import androidx.core.content.FileProvider; | ||
40 | + | ||
41 | +import java.io.File; | ||
42 | +import java.io.FileOutputStream; | ||
43 | +import java.io.IOException; | ||
44 | +import java.io.OutputStream; | ||
45 | +import java.util.HashMap; | ||
46 | + | ||
47 | +/** | ||
48 | + * PrintJob | ||
49 | + */ | ||
50 | +public class PrintingJob extends PrintDocumentAdapter { | ||
51 | + private static PrintManager printManager; | ||
52 | + private final Context context; | ||
53 | + private final PrintingPlugin printing; | ||
54 | + private PrintJob printJob; | ||
55 | + private byte[] documentData; | ||
56 | + private String jobName; | ||
57 | + private LayoutResultCallback callback; | ||
58 | + int index; | ||
59 | + | ||
60 | + PrintingJob(Context context, PrintingPlugin printing, int index) { | ||
61 | + this.context = context; | ||
62 | + this.printing = printing; | ||
63 | + this.index = index; | ||
64 | + printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE); | ||
65 | + } | ||
66 | + | ||
67 | + static HashMap<String, Object> printingInfo() { | ||
68 | + HashMap<String, Object> result = new HashMap<>(); | ||
69 | + result.put("directPrint", false); | ||
70 | + result.put("dynamicLayout", true); | ||
71 | + result.put("canPrint", true); | ||
72 | + result.put("canConvertHtml", true); | ||
73 | + result.put("canShare", true); | ||
74 | + return result; | ||
75 | + } | ||
76 | + | ||
77 | + @Override | ||
78 | + public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor parcelFileDescriptor, | ||
79 | + CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { | ||
80 | + OutputStream output = null; | ||
81 | + try { | ||
82 | + output = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); | ||
83 | + output.write(documentData, 0, documentData.length); | ||
84 | + writeResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES}); | ||
85 | + } catch (IOException e) { | ||
86 | + e.printStackTrace(); | ||
87 | + } finally { | ||
88 | + try { | ||
89 | + if (output != null) { | ||
90 | + output.close(); | ||
91 | + } | ||
92 | + } catch (IOException e) { | ||
93 | + e.printStackTrace(); | ||
94 | + } | ||
95 | + } | ||
96 | + } | ||
97 | + | ||
98 | + @Override | ||
99 | + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, | ||
100 | + CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { | ||
101 | + // Respond to cancellation request | ||
102 | + if (cancellationSignal.isCanceled()) { | ||
103 | + callback.onLayoutCancelled(); | ||
104 | + return; | ||
105 | + } | ||
106 | + | ||
107 | + this.callback = callback; | ||
108 | + | ||
109 | + PrintAttributes.MediaSize size = newAttributes.getMediaSize(); | ||
110 | + PrintAttributes.Margins margins = newAttributes.getMinMargins(); | ||
111 | + assert size != null; | ||
112 | + assert margins != null; | ||
113 | + | ||
114 | + printing.onLayout(this, size.getWidthMils() * 72.0 / 1000.0, | ||
115 | + size.getHeightMils() * 72.0 / 1000.0, margins.getLeftMils() * 72.0 / 1000.0, | ||
116 | + margins.getTopMils() * 72.0 / 1000.0, margins.getRightMils() * 72.0 / 1000.0, | ||
117 | + margins.getBottomMils() * 72.0 / 1000.0); | ||
118 | + } | ||
119 | + | ||
120 | + @Override | ||
121 | + public void onFinish() { | ||
122 | + try { | ||
123 | + while (true) { | ||
124 | + int state = printJob.getInfo().getState(); | ||
125 | + | ||
126 | + if (state == PrintJobInfo.STATE_COMPLETED) { | ||
127 | + printing.onCompleted(this, true, ""); | ||
128 | + break; | ||
129 | + } else if (state == PrintJobInfo.STATE_CANCELED) { | ||
130 | + printing.onCompleted(this, false, "User canceled"); | ||
131 | + break; | ||
132 | + } | ||
133 | + | ||
134 | + Thread.sleep(200); | ||
135 | + } | ||
136 | + } catch (Exception e) { | ||
137 | + printing.onCompleted(this, printJob != null && printJob.isCompleted(), e.getMessage()); | ||
138 | + } | ||
139 | + | ||
140 | + printJob = null; | ||
141 | + } | ||
142 | + | ||
143 | + void printPdf(@NonNull String name, Double width, Double height, Double marginLeft, | ||
144 | + Double marginTop, Double marginRight, Double marginBottom) { | ||
145 | + jobName = name; | ||
146 | + printJob = printManager.print(name, this, null); | ||
147 | + } | ||
148 | + | ||
149 | + void cancelJob() { | ||
150 | + if (callback != null) callback.onLayoutCancelled(); | ||
151 | + if (printJob != null) printJob.cancel(); | ||
152 | + } | ||
153 | + | ||
154 | + static void sharePdf(final Context context, final byte[] data, final String name) { | ||
155 | + assert name != null; | ||
156 | + | ||
157 | + try { | ||
158 | + final File externalFilesDirectory = | ||
159 | + context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); | ||
160 | + File shareFile = new File(externalFilesDirectory, name); | ||
161 | + | ||
162 | + FileOutputStream stream = new FileOutputStream(shareFile); | ||
163 | + stream.write(data); | ||
164 | + stream.close(); | ||
165 | + | ||
166 | + Uri apkURI = FileProvider.getUriForFile(context, | ||
167 | + context.getApplicationContext().getPackageName() + ".flutter.printing", | ||
168 | + shareFile); | ||
169 | + | ||
170 | + Intent shareIntent = new Intent(); | ||
171 | + shareIntent.setAction(Intent.ACTION_SEND); | ||
172 | + shareIntent.setType("application/pdf"); | ||
173 | + shareIntent.putExtra(Intent.EXTRA_STREAM, apkURI); | ||
174 | + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||
175 | + Intent chooserIntent = Intent.createChooser(shareIntent, null); | ||
176 | + context.startActivity(chooserIntent); | ||
177 | + shareFile.deleteOnExit(); | ||
178 | + } catch (IOException e) { | ||
179 | + e.printStackTrace(); | ||
180 | + } | ||
181 | + } | ||
182 | + | ||
183 | + void convertHtml(final String data, final PrintAttributes.MediaSize size, | ||
184 | + final PrintAttributes.Margins margins, final String baseUrl) { | ||
185 | + final WebView webView = new WebView(context.getApplicationContext()); | ||
186 | + | ||
187 | + webView.loadDataWithBaseURL(baseUrl, data, "text/HTML", "UTF-8", null); | ||
188 | + | ||
189 | + webView.setWebViewClient(new WebViewClient() { | ||
190 | + @Override | ||
191 | + public void onPageFinished(WebView view, String url) { | ||
192 | + super.onPageFinished(view, url); | ||
193 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
194 | + PrintAttributes attributes = | ||
195 | + new PrintAttributes.Builder() | ||
196 | + .setMediaSize(size) | ||
197 | + .setResolution( | ||
198 | + new PrintAttributes.Resolution("pdf", "pdf", 600, 600)) | ||
199 | + .setMinMargins(margins) | ||
200 | + .build(); | ||
201 | + | ||
202 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||
203 | + final PrintDocumentAdapter adapter = | ||
204 | + webView.createPrintDocumentAdapter("printing"); | ||
205 | + | ||
206 | + PdfConvert.print(context, adapter, attributes, new PdfConvert.Result() { | ||
207 | + @Override | ||
208 | + public void onSuccess(File file) { | ||
209 | + try { | ||
210 | + byte[] fileContent = PdfConvert.readFile(file); | ||
211 | + printing.onHtmlRendered(PrintingJob.this, fileContent); | ||
212 | + } catch (IOException e) { | ||
213 | + onError(e.getMessage()); | ||
214 | + } | ||
215 | + } | ||
216 | + | ||
217 | + @Override | ||
218 | + public void onError(String message) { | ||
219 | + printing.onHtmlError(PrintingJob.this, message); | ||
220 | + } | ||
221 | + }); | ||
222 | + } | ||
223 | + } | ||
224 | + } | ||
225 | + }); | ||
226 | + } | ||
227 | + | ||
228 | + void setDocument(byte[] data) { | ||
229 | + documentData = data; | ||
230 | + | ||
231 | + PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName + ".pdf") | ||
232 | + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) | ||
233 | + .build(); | ||
234 | + | ||
235 | + // Content layout reflow is complete | ||
236 | + callback.onLayoutFinished(info, true); | ||
237 | + } | ||
238 | +} |
@@ -17,32 +17,10 @@ | @@ -17,32 +17,10 @@ | ||
17 | package net.nfet.flutter.printing; | 17 | package net.nfet.flutter.printing; |
18 | 18 | ||
19 | import android.app.Activity; | 19 | import android.app.Activity; |
20 | -import android.content.Context; | ||
21 | -import android.content.Intent; | ||
22 | -import android.net.Uri; | ||
23 | -import android.os.Build; | ||
24 | -import android.os.Bundle; | ||
25 | -import android.os.CancellationSignal; | ||
26 | -import android.os.Environment; | ||
27 | -import android.os.ParcelFileDescriptor; | ||
28 | -import android.print.PageRange; | ||
29 | -import android.print.PdfConvert; | ||
30 | import android.print.PrintAttributes; | 20 | import android.print.PrintAttributes; |
31 | -import android.print.PrintDocumentAdapter; | ||
32 | -import android.print.PrintDocumentInfo; | ||
33 | -import android.print.PrintJob; | ||
34 | -import android.print.PrintJobInfo; | ||
35 | -import android.print.PrintManager; | ||
36 | -import android.print.pdf.PrintedPdfDocument; | ||
37 | -import android.webkit.WebView; | ||
38 | -import android.webkit.WebViewClient; | ||
39 | 21 | ||
40 | -import androidx.core.content.FileProvider; | 22 | +import androidx.annotation.NonNull; |
41 | 23 | ||
42 | -import java.io.File; | ||
43 | -import java.io.FileOutputStream; | ||
44 | -import java.io.IOException; | ||
45 | -import java.io.OutputStream; | ||
46 | import java.util.HashMap; | 24 | import java.util.HashMap; |
47 | 25 | ||
48 | import io.flutter.plugin.common.MethodCall; | 26 | import io.flutter.plugin.common.MethodCall; |
@@ -54,17 +32,11 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; | @@ -54,17 +32,11 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; | ||
54 | /** | 32 | /** |
55 | * PrintingPlugin | 33 | * PrintingPlugin |
56 | */ | 34 | */ |
57 | -public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHandler { | ||
58 | - private static PrintManager printManager; | 35 | +public class PrintingPlugin implements MethodCallHandler { |
59 | private final Activity activity; | 36 | private final Activity activity; |
60 | private final MethodChannel channel; | 37 | private final MethodChannel channel; |
61 | - private PrintedPdfDocument mPdfDocument; | ||
62 | - private PrintJob printJob; | ||
63 | - private byte[] documentData; | ||
64 | - private String jobName; | ||
65 | - private LayoutResultCallback callback; | ||
66 | 38 | ||
67 | - private PrintingPlugin(Activity activity, MethodChannel channel) { | 39 | + private PrintingPlugin(@NonNull Activity activity, @NonNull MethodChannel channel) { |
68 | this.activity = activity; | 40 | this.activity = activity; |
69 | this.channel = channel; | 41 | this.channel = channel; |
70 | } | 42 | } |
@@ -73,147 +45,66 @@ public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHa | @@ -73,147 +45,66 @@ public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHa | ||
73 | * Plugin registration. | 45 | * Plugin registration. |
74 | */ | 46 | */ |
75 | public static void registerWith(Registrar registrar) { | 47 | public static void registerWith(Registrar registrar) { |
76 | - try { | ||
77 | - final Activity activity = registrar.activity(); | ||
78 | - | ||
79 | - if (activity == null) { | ||
80 | - return; | ||
81 | - } | ||
82 | - | ||
83 | - final MethodChannel channel = | ||
84 | - new MethodChannel(registrar.messenger(), "net.nfet.printing"); | ||
85 | - final PrintingPlugin plugin = new PrintingPlugin(activity, channel); | ||
86 | - channel.setMethodCallHandler(plugin); | ||
87 | - | ||
88 | - if (printManager == null) { | ||
89 | - printManager = (PrintManager) activity.getSystemService(Context.PRINT_SERVICE); | ||
90 | - } | ||
91 | - } catch (Exception e) { | ||
92 | - Log.e("PrintingPlugin", "Registration failed", e); | ||
93 | - } | ||
94 | - } | ||
95 | - | ||
96 | - @Override | ||
97 | - public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor parcelFileDescriptor, | ||
98 | - CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { | ||
99 | - OutputStream output = null; | ||
100 | - try { | ||
101 | - output = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); | ||
102 | - output.write(documentData, 0, documentData.length); | ||
103 | - writeResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES}); | ||
104 | - } catch (IOException e) { | ||
105 | - e.printStackTrace(); | ||
106 | - } finally { | ||
107 | - try { | ||
108 | - if (output != null) { | ||
109 | - output.close(); | ||
110 | - } | ||
111 | - } catch (IOException e) { | ||
112 | - e.printStackTrace(); | ||
113 | - } | ||
114 | - } | ||
115 | - } | ||
116 | - | ||
117 | - @Override | ||
118 | - public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, | ||
119 | - CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { | ||
120 | - // Create a new PdfDocument with the requested page attributes | ||
121 | - mPdfDocument = new PrintedPdfDocument(activity, newAttributes); | ||
122 | - | ||
123 | - // Respond to cancellation request | ||
124 | - if (cancellationSignal.isCanceled()) { | ||
125 | - callback.onLayoutCancelled(); | ||
126 | - return; | ||
127 | - } | ||
128 | - | ||
129 | - this.callback = callback; | ||
130 | - | ||
131 | - HashMap<String, Double> args = new HashMap<>(); | ||
132 | - | ||
133 | - PrintAttributes.MediaSize size = newAttributes.getMediaSize(); | ||
134 | - args.put("width", size.getWidthMils() * 72.0 / 1000.0); | ||
135 | - args.put("height", size.getHeightMils() * 72.0 / 1000.0); | ||
136 | - | ||
137 | - PrintAttributes.Margins margins = newAttributes.getMinMargins(); | ||
138 | - args.put("marginLeft", margins.getLeftMils() * 72.0 / 1000.0); | ||
139 | - args.put("marginTop", margins.getTopMils() * 72.0 / 1000.0); | ||
140 | - args.put("marginRight", margins.getRightMils() * 72.0 / 1000.0); | ||
141 | - args.put("marginBottom", margins.getBottomMils() * 72.0 / 1000.0); | ||
142 | - | ||
143 | - channel.invokeMethod("onLayout", args); | ||
144 | - } | ||
145 | - | ||
146 | - @Override | ||
147 | - public void onFinish() { | ||
148 | - try { | ||
149 | - while (true) { | ||
150 | - int state = printJob.getInfo().getState(); | ||
151 | - | ||
152 | - if (state == PrintJobInfo.STATE_COMPLETED) { | ||
153 | - HashMap<String, Object> args = new HashMap<>(); | ||
154 | - args.put("completed", true); | ||
155 | - channel.invokeMethod("onCompleted", args); | ||
156 | - break; | ||
157 | - } else if (state == PrintJobInfo.STATE_CANCELED) { | ||
158 | - HashMap<String, Object> args = new HashMap<>(); | ||
159 | - args.put("completed", false); | ||
160 | - channel.invokeMethod("onCompleted", args); | ||
161 | - break; | ||
162 | - } | ||
163 | - | ||
164 | - Thread.sleep(200); | ||
165 | - } | ||
166 | - } catch (Exception e) { | ||
167 | - HashMap<String, Object> args = new HashMap<>(); | ||
168 | - args.put("completed", printJob != null && printJob.isCompleted()); | ||
169 | - args.put("error", e.getMessage()); | ||
170 | - channel.invokeMethod("onCompleted", args); | 48 | + Activity activity = registrar.activity(); |
49 | + if (activity == null) { | ||
50 | + return; // We can't print without an activity | ||
171 | } | 51 | } |
172 | 52 | ||
173 | - printJob = null; | ||
174 | - mPdfDocument = null; | 53 | + final MethodChannel channel = new MethodChannel(registrar.messenger(), "net.nfet.printing"); |
54 | + channel.setMethodCallHandler(new PrintingPlugin(activity, channel)); | ||
175 | } | 55 | } |
176 | 56 | ||
177 | @Override | 57 | @Override |
178 | - public void onMethodCall(MethodCall call, Result result) { | 58 | + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { |
179 | switch (call.method) { | 59 | switch (call.method) { |
180 | - case "printPdf": | ||
181 | - jobName = | ||
182 | - call.argument("name") == null ? "Document" : (String) call.argument("name"); | ||
183 | - assert jobName != null; | ||
184 | - printJob = printManager.print(jobName, this, null); | ||
185 | - result.success(0); | 60 | + case "printPdf": { |
61 | + final String name = call.argument("name"); | ||
62 | + final Double width = call.argument("width"); | ||
63 | + final Double height = call.argument("height"); | ||
64 | + final Double marginLeft = call.argument("marginLeft"); | ||
65 | + final Double marginTop = call.argument("marginTop"); | ||
66 | + final Double marginRight = call.argument("marginRight"); | ||
67 | + final Double marginBottom = call.argument("marginBottom"); | ||
68 | + | ||
69 | + final PrintingJob printJob = | ||
70 | + new PrintingJob(activity, this, (int) call.argument("job")); | ||
71 | + assert name != null; | ||
72 | + printJob.printPdf( | ||
73 | + name, width, height, marginLeft, marginTop, marginRight, marginBottom); | ||
74 | + | ||
75 | + result.success(1); | ||
186 | break; | 76 | break; |
187 | - case "writePdf": | ||
188 | - documentData = (byte[]) call.argument("doc"); | ||
189 | - | ||
190 | - // Return print information to print framework | ||
191 | - PrintDocumentInfo info = | ||
192 | - new PrintDocumentInfo.Builder(jobName + ".pdf") | ||
193 | - .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) | ||
194 | - .build(); | ||
195 | - | ||
196 | - // Content layout reflow is complete | ||
197 | - callback.onLayoutFinished(info, true); | ||
198 | - | ||
199 | - result.success(0); | ||
200 | - break; | ||
201 | - case "cancelJob": | ||
202 | - if (callback != null) callback.onLayoutCancelled(); | ||
203 | - if (printJob != null) printJob.cancel(); | ||
204 | - result.success(0); | 77 | + } |
78 | + case "cancelJob": { | ||
79 | + final PrintingJob printJob = | ||
80 | + new PrintingJob(activity, this, (int) call.argument("job")); | ||
81 | + printJob.cancelJob(); | ||
82 | + result.success(1); | ||
205 | break; | 83 | break; |
206 | - case "sharePdf": | ||
207 | - sharePdf((byte[]) call.argument("doc"), (String) call.argument("name")); | ||
208 | - result.success(0); | 84 | + } |
85 | + case "sharePdf": { | ||
86 | + final byte[] document = call.argument("doc"); | ||
87 | + final String name = call.argument("name"); | ||
88 | + PrintingJob.sharePdf(activity, document, name); | ||
89 | + result.success(1); | ||
209 | break; | 90 | break; |
210 | - case "convertHtml": | ||
211 | - double width = (double) call.argument("width"); | ||
212 | - double height = (double) call.argument("height"); | ||
213 | - double marginLeft = (double) call.argument("marginLeft"); | ||
214 | - double marginTop = (double) call.argument("marginTop"); | ||
215 | - double marginRight = (double) call.argument("marginRight"); | ||
216 | - double marginBottom = (double) call.argument("marginBottom"); | 91 | + } |
92 | + case "convertHtml": { | ||
93 | + Double width = call.argument("width"); | ||
94 | + Double height = call.argument("height"); | ||
95 | + Double marginLeft = call.argument("marginLeft"); | ||
96 | + Double marginTop = call.argument("marginTop"); | ||
97 | + Double marginRight = call.argument("marginRight"); | ||
98 | + Double marginBottom = call.argument("marginBottom"); | ||
99 | + final PrintingJob printJob = | ||
100 | + new PrintingJob(activity, this, (int) call.argument("job")); | ||
101 | + | ||
102 | + assert width != null; | ||
103 | + assert height != null; | ||
104 | + assert marginLeft != null; | ||
105 | + assert marginTop != null; | ||
106 | + assert marginRight != null; | ||
107 | + assert marginBottom != null; | ||
217 | 108 | ||
218 | PrintAttributes.Margins margins = | 109 | PrintAttributes.Margins margins = |
219 | new PrintAttributes.Margins(Double.valueOf(marginLeft * 1000.0).intValue(), | 110 | new PrintAttributes.Margins(Double.valueOf(marginLeft * 1000.0).intValue(), |
@@ -225,90 +116,82 @@ public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHa | @@ -225,90 +116,82 @@ public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHa | ||
225 | "Provided size", Double.valueOf(width * 1000.0 / 72.0).intValue(), | 116 | "Provided size", Double.valueOf(width * 1000.0 / 72.0).intValue(), |
226 | Double.valueOf(height * 1000.0 / 72.0).intValue()); | 117 | Double.valueOf(height * 1000.0 / 72.0).intValue()); |
227 | 118 | ||
228 | - convertHtml((String) call.argument("html"), size, margins, | 119 | + printJob.convertHtml((String) call.argument("html"), size, margins, |
229 | (String) call.argument("baseUrl")); | 120 | (String) call.argument("baseUrl")); |
230 | - result.success(0); | 121 | + result.success(1); |
231 | break; | 122 | break; |
123 | + } | ||
124 | + case "printingInfo": { | ||
125 | + result.success(PrintingJob.printingInfo()); | ||
126 | + break; | ||
127 | + } | ||
232 | default: | 128 | default: |
233 | result.notImplemented(); | 129 | result.notImplemented(); |
234 | break; | 130 | break; |
235 | } | 131 | } |
236 | } | 132 | } |
237 | 133 | ||
238 | - private void sharePdf(byte[] data, String name) { | ||
239 | - try { | ||
240 | - final File externalFilesDirectory = | ||
241 | - activity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); | ||
242 | - File shareFile; | ||
243 | - if (name == null) { | ||
244 | - shareFile = File.createTempFile("document-", ".pdf", externalFilesDirectory); | ||
245 | - } else { | ||
246 | - shareFile = new File(externalFilesDirectory, name); | ||
247 | - } | 134 | + /// Request the Pdf document from flutter |
135 | + void onLayout(final PrintingJob printJob, Double width, double height, double marginLeft, | ||
136 | + double marginTop, double marginRight, double marginBottom) { | ||
137 | + HashMap<String, Object> args = new HashMap<>(); | ||
138 | + args.put("width", width); | ||
139 | + args.put("height", height); | ||
248 | 140 | ||
249 | - FileOutputStream stream = new FileOutputStream(shareFile); | ||
250 | - stream.write(data); | ||
251 | - stream.close(); | 141 | + args.put("marginLeft", marginLeft); |
142 | + args.put("marginTop", marginTop); | ||
143 | + args.put("marginRight", marginRight); | ||
144 | + args.put("marginBottom", marginBottom); | ||
145 | + args.put("job", printJob.index); | ||
146 | + | ||
147 | + channel.invokeMethod("onLayout", args, new Result() { | ||
148 | + @Override | ||
149 | + public void success(Object result) { | ||
150 | + if (result instanceof byte[]) { | ||
151 | + printJob.setDocument((byte[]) result); | ||
152 | + } else { | ||
153 | + printJob.cancelJob(); | ||
154 | + } | ||
155 | + } | ||
252 | 156 | ||
253 | - Uri apkURI = FileProvider.getUriForFile(activity, | ||
254 | - activity.getApplicationContext().getPackageName() + ".flutter.printing", | ||
255 | - shareFile); | 157 | + @Override |
158 | + public void error(String errorCode, String errorMessage, Object errorDetails) { | ||
159 | + printJob.cancelJob(); | ||
160 | + } | ||
256 | 161 | ||
257 | - Intent shareIntent = new Intent(); | ||
258 | - shareIntent.setAction(Intent.ACTION_SEND); | ||
259 | - shareIntent.setType("application/pdf"); | ||
260 | - shareIntent.putExtra(Intent.EXTRA_STREAM, apkURI); | ||
261 | - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||
262 | - Intent chooserIntent = Intent.createChooser(shareIntent, null); | ||
263 | - activity.startActivity(chooserIntent); | ||
264 | - shareFile.deleteOnExit(); | ||
265 | - } catch (IOException e) { | ||
266 | - e.printStackTrace(); | ||
267 | - } | 162 | + @Override |
163 | + public void notImplemented() { | ||
164 | + printJob.cancelJob(); | ||
165 | + } | ||
166 | + }); | ||
268 | } | 167 | } |
269 | 168 | ||
270 | - private void convertHtml(String data, final PrintAttributes.MediaSize size, | ||
271 | - final PrintAttributes.Margins margins, String baseUrl) { | ||
272 | - final WebView webView = new WebView(activity.getApplicationContext()); | 169 | + /// send completion status to flutter |
170 | + void onCompleted(PrintingJob printJob, boolean completed, String error) { | ||
171 | + HashMap<String, Object> args = new HashMap<>(); | ||
172 | + args.put("completed", completed); | ||
273 | 173 | ||
274 | - webView.loadDataWithBaseURL(baseUrl, data, "text/HTML", "UTF-8", null); | 174 | + args.put("error", error); |
175 | + args.put("job", printJob.index); | ||
275 | 176 | ||
276 | - webView.setWebViewClient(new WebViewClient() { | ||
277 | - @Override | ||
278 | - public void onPageFinished(WebView view, String url) { | ||
279 | - super.onPageFinished(view, url); | ||
280 | - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
281 | - PrintAttributes attributes = | ||
282 | - new PrintAttributes.Builder() | ||
283 | - .setMediaSize(size) | ||
284 | - .setResolution( | ||
285 | - new PrintAttributes.Resolution("pdf", "pdf", 600, 600)) | ||
286 | - .setMinMargins(margins) | ||
287 | - .build(); | 177 | + channel.invokeMethod("onCompleted", args); |
178 | + } | ||
288 | 179 | ||
289 | - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||
290 | - final PrintDocumentAdapter adapter = | ||
291 | - webView.createPrintDocumentAdapter("printing"); | 180 | + /// send html to pdf data result to flutter |
181 | + void onHtmlRendered(PrintingJob printJob, byte[] pdfData) { | ||
182 | + HashMap<String, Object> args = new HashMap<>(); | ||
183 | + args.put("doc", pdfData); | ||
184 | + args.put("job", printJob.index); | ||
292 | 185 | ||
293 | - PdfConvert.print(activity, adapter, attributes, new PdfConvert.Result() { | ||
294 | - @Override | ||
295 | - public void onSuccess(File file) { | ||
296 | - try { | ||
297 | - byte[] fileContent = PdfConvert.readFile(file); | ||
298 | - channel.invokeMethod("onHtmlRendered", fileContent); | ||
299 | - } catch (IOException e) { | ||
300 | - onError(e.getMessage()); | ||
301 | - } | ||
302 | - } | 186 | + channel.invokeMethod("onHtmlRendered", args); |
187 | + } | ||
303 | 188 | ||
304 | - @Override | ||
305 | - public void onError(String message) { | ||
306 | - channel.invokeMethod("onHtmlError", message); | ||
307 | - } | ||
308 | - }); | ||
309 | - } | ||
310 | - } | ||
311 | - } | ||
312 | - }); | 189 | + /// send html to pdf conversion error to flutter |
190 | + void onHtmlError(PrintingJob printJob, String error) { | ||
191 | + HashMap<String, Object> args = new HashMap<>(); | ||
192 | + args.put("error", error); | ||
193 | + args.put("job", printJob.index); | ||
194 | + | ||
195 | + channel.invokeMethod("onHtmlError", args); | ||
313 | } | 196 | } |
314 | } | 197 | } |
@@ -68,6 +68,9 @@ | @@ -68,6 +68,9 @@ | ||
68 | **/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist | 68 | **/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist |
69 | **/ios/Podfile.lock | 69 | **/ios/Podfile.lock |
70 | **/ios/Podfile | 70 | **/ios/Podfile |
71 | +**/ios/Flutter/Flutter.podspec | ||
72 | +lib/generated_plugin_registrant.dart | ||
73 | +**/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist | ||
71 | 74 | ||
72 | # Exceptions to above rules. | 75 | # Exceptions to above rules. |
73 | !**/ios/**/default.mode1v3 | 76 | !**/ios/**/default.mode1v3 |
@@ -180,13 +180,13 @@ | @@ -180,13 +180,13 @@ | ||
180 | TargetAttributes = { | 180 | TargetAttributes = { |
181 | 97C146ED1CF9000F007C117D = { | 181 | 97C146ED1CF9000F007C117D = { |
182 | CreatedOnToolsVersion = 7.3.1; | 182 | CreatedOnToolsVersion = 7.3.1; |
183 | - LastSwiftMigration = 1010; | 183 | + LastSwiftMigration = 1120; |
184 | }; | 184 | }; |
185 | }; | 185 | }; |
186 | }; | 186 | }; |
187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; | 187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; |
188 | compatibilityVersion = "Xcode 3.2"; | 188 | compatibilityVersion = "Xcode 3.2"; |
189 | - developmentRegion = English; | 189 | + developmentRegion = en; |
190 | hasScannedForEncodings = 0; | 190 | hasScannedForEncodings = 0; |
191 | knownRegions = ( | 191 | knownRegions = ( |
192 | en, | 192 | en, |
@@ -386,7 +386,7 @@ | @@ -386,7 +386,7 @@ | ||
386 | ); | 386 | ); |
387 | PRODUCT_BUNDLE_IDENTIFIER = net.nfet.flutter.printingExample; | 387 | PRODUCT_BUNDLE_IDENTIFIER = net.nfet.flutter.printingExample; |
388 | PRODUCT_NAME = "$(TARGET_NAME)"; | 388 | PRODUCT_NAME = "$(TARGET_NAME)"; |
389 | - SWIFT_VERSION = 4.2; | 389 | + SWIFT_VERSION = 5.0; |
390 | VERSIONING_SYSTEM = "apple-generic"; | 390 | VERSIONING_SYSTEM = "apple-generic"; |
391 | }; | 391 | }; |
392 | name = Profile; | 392 | name = Profile; |
@@ -520,7 +520,7 @@ | @@ -520,7 +520,7 @@ | ||
520 | PRODUCT_NAME = "$(TARGET_NAME)"; | 520 | PRODUCT_NAME = "$(TARGET_NAME)"; |
521 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | 521 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; |
522 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | 522 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; |
523 | - SWIFT_VERSION = 4.2; | 523 | + SWIFT_VERSION = 5.0; |
524 | VERSIONING_SYSTEM = "apple-generic"; | 524 | VERSIONING_SYSTEM = "apple-generic"; |
525 | }; | 525 | }; |
526 | name = Debug; | 526 | name = Debug; |
@@ -546,7 +546,7 @@ | @@ -546,7 +546,7 @@ | ||
546 | PRODUCT_BUNDLE_IDENTIFIER = net.nfet.flutter.printingExample; | 546 | PRODUCT_BUNDLE_IDENTIFIER = net.nfet.flutter.printingExample; |
547 | PRODUCT_NAME = "$(TARGET_NAME)"; | 547 | PRODUCT_NAME = "$(TARGET_NAME)"; |
548 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | 548 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; |
549 | - SWIFT_VERSION = 4.2; | 549 | + SWIFT_VERSION = 5.0; |
550 | VERSIONING_SYSTEM = "apple-generic"; | 550 | VERSIONING_SYSTEM = "apple-generic"; |
551 | }; | 551 | }; |
552 | name = Release; | 552 | name = Release; |
@@ -31,6 +31,30 @@ class MyAppState extends State<MyApp> { | @@ -31,6 +31,30 @@ class MyAppState extends State<MyApp> { | ||
31 | final GlobalKey<State<StatefulWidget>> previewContainer = GlobalKey(); | 31 | final GlobalKey<State<StatefulWidget>> previewContainer = GlobalKey(); |
32 | 32 | ||
33 | Printer selectedPrinter; | 33 | Printer selectedPrinter; |
34 | + PrintingInfo printingInfo; | ||
35 | + | ||
36 | + @override | ||
37 | + void initState() { | ||
38 | + Printing.info().then((PrintingInfo info) { | ||
39 | + setState(() { | ||
40 | + printingInfo = info; | ||
41 | + }); | ||
42 | + }); | ||
43 | + super.initState(); | ||
44 | + } | ||
45 | + | ||
46 | + void _showPrintedToast(bool printed) { | ||
47 | + final ScaffoldState scaffold = Scaffold.of(shareWidget.currentContext); | ||
48 | + if (printed) { | ||
49 | + scaffold.showSnackBar(const SnackBar( | ||
50 | + content: Text('Document printed successfully'), | ||
51 | + )); | ||
52 | + } else { | ||
53 | + scaffold.showSnackBar(const SnackBar( | ||
54 | + content: Text('Document not printed'), | ||
55 | + )); | ||
56 | + } | ||
57 | + } | ||
34 | 58 | ||
35 | Future<void> _printPdf() async { | 59 | Future<void> _printPdf() async { |
36 | print('Print ...'); | 60 | print('Print ...'); |
@@ -38,7 +62,7 @@ class MyAppState extends State<MyApp> { | @@ -38,7 +62,7 @@ class MyAppState extends State<MyApp> { | ||
38 | onLayout: (PdfPageFormat format) async => | 62 | onLayout: (PdfPageFormat format) async => |
39 | (await generateDocument(format)).save()); | 63 | (await generateDocument(format)).save()); |
40 | 64 | ||
41 | - print('Document printed: $result'); | 65 | + _showPrintedToast(result); |
42 | } | 66 | } |
43 | 67 | ||
44 | Future<void> _saveAsFile() async { | 68 | Future<void> _saveAsFile() async { |
@@ -84,7 +108,7 @@ class MyAppState extends State<MyApp> { | @@ -84,7 +108,7 @@ class MyAppState extends State<MyApp> { | ||
84 | onLayout: (PdfPageFormat format) async => | 108 | onLayout: (PdfPageFormat format) async => |
85 | (await generateDocument(PdfPageFormat.letter)).save()); | 109 | (await generateDocument(PdfPageFormat.letter)).save()); |
86 | 110 | ||
87 | - print('Document printed: $result'); | 111 | + _showPrintedToast(result); |
88 | } | 112 | } |
89 | 113 | ||
90 | Future<void> _sharePdf() async { | 114 | Future<void> _sharePdf() async { |
@@ -112,7 +136,8 @@ class MyAppState extends State<MyApp> { | @@ -112,7 +136,8 @@ class MyAppState extends State<MyApp> { | ||
112 | await im.toByteData(format: ui.ImageByteFormat.rawRgba); | 136 | await im.toByteData(format: ui.ImageByteFormat.rawRgba); |
113 | print('Print Screen ${im.width}x${im.height} ...'); | 137 | print('Print Screen ${im.width}x${im.height} ...'); |
114 | 138 | ||
115 | - Printing.layoutPdf(onLayout: (PdfPageFormat format) { | 139 | + final bool result = |
140 | + await Printing.layoutPdf(onLayout: (PdfPageFormat format) { | ||
116 | final pdf.Document document = pdf.Document(); | 141 | final pdf.Document document = pdf.Document(); |
117 | 142 | ||
118 | final PdfImage image = PdfImage(document.document, | 143 | final PdfImage image = PdfImage(document.document, |
@@ -132,24 +157,32 @@ class MyAppState extends State<MyApp> { | @@ -132,24 +157,32 @@ class MyAppState extends State<MyApp> { | ||
132 | 157 | ||
133 | return document.save(); | 158 | return document.save(); |
134 | }); | 159 | }); |
160 | + | ||
161 | + _showPrintedToast(result); | ||
135 | } | 162 | } |
136 | 163 | ||
137 | Future<void> _printHtml() async { | 164 | Future<void> _printHtml() async { |
138 | print('Print html ...'); | 165 | print('Print html ...'); |
139 | - await Printing.layoutPdf(onLayout: (PdfPageFormat format) async { | 166 | + final bool result = |
167 | + await Printing.layoutPdf(onLayout: (PdfPageFormat format) async { | ||
140 | final String html = await rootBundle.loadString('assets/example.html'); | 168 | final String html = await rootBundle.loadString('assets/example.html'); |
141 | return await Printing.convertHtml(format: format, html: html); | 169 | return await Printing.convertHtml(format: format, html: html); |
142 | }); | 170 | }); |
171 | + | ||
172 | + _showPrintedToast(result); | ||
143 | } | 173 | } |
144 | 174 | ||
145 | Future<void> _printMarkdown() async { | 175 | Future<void> _printMarkdown() async { |
146 | print('Print Markdown ...'); | 176 | print('Print Markdown ...'); |
147 | - await Printing.layoutPdf(onLayout: (PdfPageFormat format) async { | 177 | + final bool result = |
178 | + await Printing.layoutPdf(onLayout: (PdfPageFormat format) async { | ||
148 | final String md = await rootBundle.loadString('assets/example.md'); | 179 | final String md = await rootBundle.loadString('assets/example.md'); |
149 | final String html = markdown.markdownToHtml(md, | 180 | final String html = markdown.markdownToHtml(md, |
150 | extensionSet: markdown.ExtensionSet.gitHubWeb); | 181 | extensionSet: markdown.ExtensionSet.gitHubWeb); |
151 | return await Printing.convertHtml(format: format, html: html); | 182 | return await Printing.convertHtml(format: format, html: html); |
152 | }); | 183 | }); |
184 | + | ||
185 | + _showPrintedToast(result); | ||
153 | } | 186 | } |
154 | 187 | ||
155 | @override | 188 | @override |
@@ -170,37 +203,52 @@ class MyAppState extends State<MyApp> { | @@ -170,37 +203,52 @@ class MyAppState extends State<MyApp> { | ||
170 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, | 203 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
171 | children: <Widget>[ | 204 | children: <Widget>[ |
172 | RaisedButton( | 205 | RaisedButton( |
173 | - child: const Text('Print Document'), onPressed: _printPdf), | 206 | + child: const Text('Print Document'), |
207 | + onPressed: printingInfo?.canPrint ?? false ? _printPdf : null, | ||
208 | + ), | ||
174 | Row( | 209 | Row( |
175 | mainAxisSize: MainAxisSize.min, | 210 | mainAxisSize: MainAxisSize.min, |
176 | children: <Widget>[ | 211 | children: <Widget>[ |
177 | RaisedButton( | 212 | RaisedButton( |
178 | - key: pickWidget, | ||
179 | - child: const Text('Pick Printer'), | ||
180 | - onPressed: _pickPrinter), | 213 | + key: pickWidget, |
214 | + child: const Text('Pick Printer'), | ||
215 | + onPressed: printingInfo?.directPrint ?? false | ||
216 | + ? _pickPrinter | ||
217 | + : null, | ||
218 | + ), | ||
181 | const SizedBox(width: 10), | 219 | const SizedBox(width: 10), |
182 | RaisedButton( | 220 | RaisedButton( |
183 | - child: Text(selectedPrinter == null | ||
184 | - ? 'Direct Print' | ||
185 | - : 'Print to $selectedPrinter'), | ||
186 | - onPressed: | ||
187 | - selectedPrinter != null ? _directPrintPdf : null), | 221 | + child: Text(selectedPrinter == null |
222 | + ? 'Direct Print' | ||
223 | + : 'Print to $selectedPrinter'), | ||
224 | + onPressed: | ||
225 | + selectedPrinter != null ? _directPrintPdf : null, | ||
226 | + ), | ||
188 | ], | 227 | ], |
189 | ), | 228 | ), |
190 | RaisedButton( | 229 | RaisedButton( |
191 | - key: shareWidget, | ||
192 | - child: const Text('Share Document'), | ||
193 | - onPressed: _sharePdf), | 230 | + key: shareWidget, |
231 | + child: const Text('Share Document'), | ||
232 | + onPressed: printingInfo?.canShare ?? false ? _sharePdf : null, | ||
233 | + ), | ||
194 | RaisedButton( | 234 | RaisedButton( |
195 | - child: const Text('Print Screenshot'), | ||
196 | - onPressed: _printScreen), | 235 | + child: const Text('Print Screenshot'), |
236 | + onPressed: | ||
237 | + printingInfo?.canPrint ?? false ? _printScreen : null, | ||
238 | + ), | ||
197 | RaisedButton( | 239 | RaisedButton( |
198 | child: const Text('Save to file'), onPressed: _saveAsFile), | 240 | child: const Text('Save to file'), onPressed: _saveAsFile), |
199 | RaisedButton( | 241 | RaisedButton( |
200 | - child: const Text('Print Html'), onPressed: _printHtml), | 242 | + child: const Text('Print Html'), |
243 | + onPressed: | ||
244 | + printingInfo?.canConvertHtml ?? false ? _printHtml : null, | ||
245 | + ), | ||
201 | RaisedButton( | 246 | RaisedButton( |
202 | - child: const Text('Print Markdown'), | ||
203 | - onPressed: _printMarkdown), | 247 | + child: const Text('Print Markdown'), |
248 | + onPressed: printingInfo?.canConvertHtml ?? false | ||
249 | + ? _printMarkdown | ||
250 | + : null, | ||
251 | + ), | ||
204 | if (canDebug) | 252 | if (canDebug) |
205 | Row( | 253 | Row( |
206 | mainAxisSize: MainAxisSize.min, | 254 | mainAxisSize: MainAxisSize.min, |
printing/ios/Assets/.gitkeep
deleted
100644 → 0
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 | ||
18 | - | ||
19 | -func dataProviderReleaseDataCallback(info _: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size _: Int) { | ||
20 | - data.deallocate() | ||
21 | -} | ||
22 | - | ||
23 | -class PdfPrintPageRenderer: UIPrintPageRenderer { | ||
24 | - private var channel: FlutterMethodChannel? | ||
25 | - private var pdfDocument: CGPDFDocument? | ||
26 | - private var lock: NSLock? | ||
27 | - private var mustLayout: Bool = true | ||
28 | - | ||
29 | - init(_ channel: FlutterMethodChannel?, data: Data?) { | ||
30 | - super.init() | ||
31 | - self.channel = channel | ||
32 | - pdfDocument = nil | ||
33 | - if data != nil { | ||
34 | - setDocument(data) | ||
35 | - mustLayout = false | ||
36 | - } | ||
37 | - lock = NSLock() | ||
38 | - } | ||
39 | - | ||
40 | - override func drawPage(at pageIndex: Int, in _: CGRect) { | ||
41 | - let ctx = UIGraphicsGetCurrentContext() | ||
42 | - let page = pdfDocument?.page(at: pageIndex + 1) | ||
43 | - ctx?.scaleBy(x: 1.0, y: -1.0) | ||
44 | - ctx?.translateBy(x: 0.0, y: -paperRect.size.height) | ||
45 | - if page != nil { | ||
46 | - ctx?.drawPDFPage(page!) | ||
47 | - } | ||
48 | - } | ||
49 | - | ||
50 | - func cancelJob() { | ||
51 | - pdfDocument = nil | ||
52 | - lock?.unlock() | ||
53 | - } | ||
54 | - | ||
55 | - func setDocument(_ data: Data?) { | ||
56 | - let bytesPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data?.count ?? 0) | ||
57 | - data?.copyBytes(to: bytesPointer, count: data?.count ?? 0) | ||
58 | - let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback) | ||
59 | - pdfDocument = CGPDFDocument(dataProvider!) | ||
60 | - lock?.unlock() | ||
61 | - } | ||
62 | - | ||
63 | - override var numberOfPages: Int { | ||
64 | - let width = NSNumber(value: Double(paperRect.size.width)) | ||
65 | - let height = NSNumber(value: Double(paperRect.size.height)) | ||
66 | - let marginLeft = NSNumber(value: Double(printableRect.origin.x)) | ||
67 | - let marginTop = NSNumber(value: Double(printableRect.origin.y)) | ||
68 | - let marginRight = NSNumber(value: Double(paperRect.size.width - (printableRect.origin.x + printableRect.size.width))) | ||
69 | - let marginBottom = NSNumber(value: Double(paperRect.size.height - (printableRect.origin.y + printableRect.size.height))) | ||
70 | - | ||
71 | - let arg = [ | ||
72 | - "width": width, | ||
73 | - "height": height, | ||
74 | - "marginLeft": marginLeft, | ||
75 | - "marginTop": marginTop, | ||
76 | - "marginRight": marginRight, | ||
77 | - "marginBottom": marginBottom, | ||
78 | - ] | ||
79 | - | ||
80 | - if mustLayout { | ||
81 | - lock?.lock() | ||
82 | - channel?.invokeMethod("onLayout", arguments: arg) | ||
83 | - lock?.lock() | ||
84 | - lock?.unlock() | ||
85 | - } | ||
86 | - | ||
87 | - let pages = pdfDocument?.numberOfPages ?? 0 | ||
88 | - | ||
89 | - return pages | ||
90 | - } | ||
91 | - | ||
92 | - var pageArgs: [String: NSNumber] { | ||
93 | - let width = NSNumber(value: Double(paperRect.size.width)) | ||
94 | - let height = NSNumber(value: Double(paperRect.size.height)) | ||
95 | - let marginLeft = NSNumber(value: Double(printableRect.origin.x)) | ||
96 | - let marginTop = NSNumber(value: Double(printableRect.origin.y)) | ||
97 | - let marginRight = NSNumber(value: Double(paperRect.size.width - (printableRect.origin.x + printableRect.size.width))) | ||
98 | - let marginBottom = NSNumber(value: Double(paperRect.size.height - (printableRect.origin.y + printableRect.size.height))) | ||
99 | - | ||
100 | - return [ | ||
101 | - "width": width, | ||
102 | - "height": height, | ||
103 | - "marginLeft": marginLeft, | ||
104 | - "marginTop": marginTop, | ||
105 | - "marginRight": marginRight, | ||
106 | - "marginBottom": marginBottom, | ||
107 | - ] | ||
108 | - } | ||
109 | -} |
printing/ios/Classes/PrintJob.swift
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 | ||
18 | +import WebKit | ||
19 | + | ||
20 | +func dataProviderReleaseDataCallback(info _: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size _: Int) { | ||
21 | + data.deallocate() | ||
22 | +} | ||
23 | + | ||
24 | +public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate { | ||
25 | + private var printing: PrintingPlugin | ||
26 | + public var index: Int | ||
27 | + private var pdfDocument: CGPDFDocument? | ||
28 | + private var urlObservation: NSKeyValueObservation? | ||
29 | + private var jobName: String? | ||
30 | + | ||
31 | + public init(printing: PrintingPlugin, index: Int) { | ||
32 | + self.printing = printing | ||
33 | + self.index = index | ||
34 | + pdfDocument = nil | ||
35 | + super.init() | ||
36 | + } | ||
37 | + | ||
38 | + public override func drawPage(at pageIndex: Int, in _: CGRect) { | ||
39 | + let ctx = UIGraphicsGetCurrentContext() | ||
40 | + let page = pdfDocument?.page(at: pageIndex + 1) | ||
41 | + ctx?.scaleBy(x: 1.0, y: -1.0) | ||
42 | + ctx?.translateBy(x: 0.0, y: -paperRect.size.height) | ||
43 | + if page != nil { | ||
44 | + ctx?.drawPDFPage(page!) | ||
45 | + } | ||
46 | + } | ||
47 | + | ||
48 | + func cancelJob() { | ||
49 | + pdfDocument = nil | ||
50 | + } | ||
51 | + | ||
52 | + func setDocument(_ data: Data?) { | ||
53 | + let bytesPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data?.count ?? 0) | ||
54 | + data?.copyBytes(to: bytesPointer, count: data?.count ?? 0) | ||
55 | + let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback) | ||
56 | + pdfDocument = CGPDFDocument(dataProvider!) | ||
57 | + | ||
58 | + let controller = UIPrintInteractionController.shared | ||
59 | + controller.delegate = self | ||
60 | + | ||
61 | + let printInfo = UIPrintInfo.printInfo() | ||
62 | + printInfo.jobName = jobName! | ||
63 | + printInfo.outputType = .general | ||
64 | + controller.printInfo = printInfo | ||
65 | + controller.printPageRenderer = self | ||
66 | + controller.present(animated: true, completionHandler: completionHandler) | ||
67 | + } | ||
68 | + | ||
69 | + public override var numberOfPages: Int { | ||
70 | + let pages = pdfDocument?.numberOfPages ?? 0 | ||
71 | + return pages | ||
72 | + } | ||
73 | + | ||
74 | + func completionHandler(printController _: UIPrintInteractionController, completed: Bool, error: Error?) { | ||
75 | + if !completed, error != nil { | ||
76 | + print("Unable to print: \(error?.localizedDescription ?? "unknown error")") | ||
77 | + } | ||
78 | + | ||
79 | + printing.onCompleted(printJob: self, completed: completed, error: error?.localizedDescription as NSString?) | ||
80 | + } | ||
81 | + | ||
82 | + func directPrintPdf(name: String, data: Data, withPrinter printerID: String) { | ||
83 | + let printing = UIPrintInteractionController.isPrintingAvailable | ||
84 | + if !printing { | ||
85 | + self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available") | ||
86 | + return | ||
87 | + } | ||
88 | + | ||
89 | + let controller = UIPrintInteractionController.shared | ||
90 | + | ||
91 | + let printInfo = UIPrintInfo.printInfo() | ||
92 | + printInfo.jobName = name | ||
93 | + printInfo.outputType = .general | ||
94 | + controller.printInfo = printInfo | ||
95 | + controller.printingItem = data | ||
96 | + let printerURL = URL(string: printerID) | ||
97 | + | ||
98 | + if printerURL == nil { | ||
99 | + self.printing.onCompleted(printJob: self, completed: false, error: "Unable to find printer URL") | ||
100 | + return | ||
101 | + } | ||
102 | + | ||
103 | + let printer = UIPrinter(url: printerURL!) | ||
104 | + controller.print(to: printer, completionHandler: completionHandler) | ||
105 | + } | ||
106 | + | ||
107 | + func printPdf(name: String, withPageSize size: CGSize, andMargin margin: CGRect) { | ||
108 | + let printing = UIPrintInteractionController.isPrintingAvailable | ||
109 | + if !printing { | ||
110 | + self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available") | ||
111 | + return | ||
112 | + } | ||
113 | + | ||
114 | + jobName = name | ||
115 | + | ||
116 | + self.printing.onLayout( | ||
117 | + printJob: self, | ||
118 | + width: size.width, | ||
119 | + height: size.height, | ||
120 | + marginLeft: margin.minX, | ||
121 | + marginTop: margin.minY, | ||
122 | + marginRight: size.width - margin.maxX, | ||
123 | + marginBottom: size.height - margin.maxY | ||
124 | + ) | ||
125 | + } | ||
126 | + | ||
127 | + static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) { | ||
128 | + let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) | ||
129 | + let fileURL = tmpDirURL.appendingPathComponent(name) | ||
130 | + | ||
131 | + do { | ||
132 | + try data.write(to: fileURL, options: .atomic) | ||
133 | + } catch { | ||
134 | + print("sharePdf error: \(error.localizedDescription)") | ||
135 | + return | ||
136 | + } | ||
137 | + | ||
138 | + let activityViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) | ||
139 | + if UI_USER_INTERFACE_IDIOM() == .pad { | ||
140 | + let controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController | ||
141 | + activityViewController.popoverPresentationController?.sourceView = controller?.view | ||
142 | + activityViewController.popoverPresentationController?.sourceRect = rect | ||
143 | + } | ||
144 | + UIApplication.shared.keyWindow?.rootViewController?.present(activityViewController, animated: true) | ||
145 | + } | ||
146 | + | ||
147 | + func convertHtml(_ data: String, withPageSize rect: CGRect, andMargin margin: CGRect, andBaseUrl baseUrl: URL?) { | ||
148 | + let viewController = UIApplication.shared.delegate?.window?!.rootViewController | ||
149 | + let wkWebView = WKWebView(frame: viewController!.view.bounds) | ||
150 | + wkWebView.isHidden = true | ||
151 | + wkWebView.tag = 100 | ||
152 | + viewController?.view.addSubview(wkWebView) | ||
153 | + wkWebView.loadHTMLString(data, baseURL: baseUrl ?? Bundle.main.bundleURL) | ||
154 | + | ||
155 | + urlObservation = wkWebView.observe(\.isLoading, changeHandler: { _, _ in | ||
156 | + // this is workaround for issue with loading local images | ||
157 | + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { | ||
158 | + // assign the print formatter to the print page renderer | ||
159 | + let renderer = UIPrintPageRenderer() | ||
160 | + renderer.addPrintFormatter(wkWebView.viewPrintFormatter(), startingAtPageAt: 0) | ||
161 | + | ||
162 | + // assign paperRect and printableRect values | ||
163 | + renderer.setValue(rect, forKey: "paperRect") | ||
164 | + renderer.setValue(margin, forKey: "printableRect") | ||
165 | + | ||
166 | + // create pdf context and draw each page | ||
167 | + let pdfData = NSMutableData() | ||
168 | + UIGraphicsBeginPDFContextToData(pdfData, rect, nil) | ||
169 | + | ||
170 | + for i in 0 ..< renderer.numberOfPages { | ||
171 | + UIGraphicsBeginPDFPage() | ||
172 | + renderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds()) | ||
173 | + } | ||
174 | + | ||
175 | + UIGraphicsEndPDFContext() | ||
176 | + | ||
177 | + if let viewWithTag = viewController?.view.viewWithTag(wkWebView.tag) { | ||
178 | + viewWithTag.removeFromSuperview() // remove hidden webview when pdf is generated | ||
179 | + | ||
180 | + // clear WKWebView cache | ||
181 | + if #available(iOS 9.0, *) { | ||
182 | + WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in | ||
183 | + records.forEach { record in | ||
184 | + WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {}) | ||
185 | + } | ||
186 | + } | ||
187 | + } | ||
188 | + } | ||
189 | + | ||
190 | + // dispose urlObservation | ||
191 | + self.urlObservation = nil | ||
192 | + self.printing.onHtmlRendered(printJob: self, pdfData: pdfData as Data) | ||
193 | + } | ||
194 | + }) | ||
195 | + } | ||
196 | + | ||
197 | + static func pickPrinter(result: @escaping FlutterResult, withSourceRect rect: CGRect) { | ||
198 | + let controller = UIPrinterPickerController(initiallySelectedPrinter: nil) | ||
199 | + | ||
200 | + let pickPrinterCompletionHandler: UIPrinterPickerController.CompletionHandler = { | ||
201 | + (printerPickerController: UIPrinterPickerController, completed: Bool, error: Error?) in | ||
202 | + if !completed, error != nil { | ||
203 | + print("Unable to pick printer: \(error?.localizedDescription ?? "unknown error")") | ||
204 | + result(nil) | ||
205 | + return | ||
206 | + } | ||
207 | + | ||
208 | + if printerPickerController.selectedPrinter == nil { | ||
209 | + result(nil) | ||
210 | + return | ||
211 | + } | ||
212 | + | ||
213 | + let printer = printerPickerController.selectedPrinter! | ||
214 | + let data: NSDictionary = [ | ||
215 | + "url": printer.url.absoluteString as Any, | ||
216 | + "name": printer.displayName as Any, | ||
217 | + "model": printer.makeAndModel as Any, | ||
218 | + "location": printer.displayLocation as Any, | ||
219 | + ] | ||
220 | + result(data) | ||
221 | + } | ||
222 | + | ||
223 | + if UI_USER_INTERFACE_IDIOM() == .pad { | ||
224 | + let viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController | ||
225 | + if viewController != nil { | ||
226 | + controller.present(from: rect, in: viewController!.view, animated: true, completionHandler: pickPrinterCompletionHandler) | ||
227 | + return | ||
228 | + } | ||
229 | + } | ||
230 | + | ||
231 | + controller.present(animated: true, completionHandler: pickPrinterCompletionHandler) | ||
232 | + } | ||
233 | + | ||
234 | + public static func printingInfo() -> NSDictionary { | ||
235 | + let data: NSDictionary = [ | ||
236 | + "directPrint": true, | ||
237 | + "dynamicLayout": false, | ||
238 | + "canPrint": true, | ||
239 | + "canConvertHtml": true, | ||
240 | + "canShare": true, | ||
241 | + ] | ||
242 | + return data | ||
243 | + } | ||
244 | +} |
@@ -15,57 +15,66 @@ | @@ -15,57 +15,66 @@ | ||
15 | */ | 15 | */ |
16 | 16 | ||
17 | import Flutter | 17 | import Flutter |
18 | -import UIKit | ||
19 | -import WebKit | 18 | +import Foundation |
20 | 19 | ||
21 | -public class PrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionControllerDelegate { | ||
22 | - private var channel: FlutterMethodChannel? | ||
23 | - private var renderer: PdfPrintPageRenderer? | ||
24 | - private var urlObservation: NSKeyValueObservation? | 20 | +public class PrintingPlugin: NSObject, FlutterPlugin { |
21 | + private var channel: FlutterMethodChannel | ||
25 | 22 | ||
26 | - init(_ channel: FlutterMethodChannel?) { | ||
27 | - super.init() | 23 | + init(_ channel: FlutterMethodChannel) { |
28 | self.channel = channel | 24 | self.channel = channel |
29 | - renderer = nil | 25 | + super.init() |
30 | } | 26 | } |
31 | 27 | ||
28 | + /// Entry point | ||
32 | public static func register(with registrar: FlutterPluginRegistrar) { | 29 | public static func register(with registrar: FlutterPluginRegistrar) { |
33 | let channel = FlutterMethodChannel(name: "net.nfet.printing", binaryMessenger: registrar.messenger()) | 30 | let channel = FlutterMethodChannel(name: "net.nfet.printing", binaryMessenger: registrar.messenger()) |
34 | let instance = PrintingPlugin(channel) | 31 | let instance = PrintingPlugin(channel) |
35 | registrar.addMethodCallDelegate(instance, channel: channel) | 32 | registrar.addMethodCallDelegate(instance, channel: channel) |
36 | } | 33 | } |
37 | 34 | ||
35 | + /// Flutter method handlers | ||
38 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { | 36 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
39 | let args = call.arguments! as! [String: Any] | 37 | let args = call.arguments! as! [String: Any] |
40 | if call.method == "printPdf" { | 38 | if call.method == "printPdf" { |
41 | - let name = args["name"] as? String ?? "" | ||
42 | - let object = args["doc"] as? FlutterStandardTypedData | ||
43 | - printPdf(name, data: object?.data) | 39 | + let name = args["name"] as! String |
40 | + let width = CGFloat((args["width"] as? NSNumber)?.floatValue ?? 0.0) | ||
41 | + let height = CGFloat((args["height"] as? NSNumber)?.floatValue ?? 0.0) | ||
42 | + let marginLeft = CGFloat((args["marginLeft"] as? NSNumber)?.floatValue ?? 0.0) | ||
43 | + let marginTop = CGFloat((args["marginTop"] as? NSNumber)?.floatValue ?? 0.0) | ||
44 | + let marginRight = CGFloat((args["marginRight"] as? NSNumber)?.floatValue ?? 0.0) | ||
45 | + let marginBottom = CGFloat((args["marginBottom"] as? NSNumber)?.floatValue ?? 0.0) | ||
46 | + let printJob = PrintJob(printing: self, index: args["job"] as! Int) | ||
47 | + printJob.printPdf(name: name, | ||
48 | + withPageSize: CGSize( | ||
49 | + width: width, | ||
50 | + height: height | ||
51 | + ), | ||
52 | + andMargin: CGRect( | ||
53 | + x: marginLeft, | ||
54 | + y: marginTop, | ||
55 | + width: width - marginRight - marginLeft, | ||
56 | + height: height - marginBottom - marginTop | ||
57 | + )) | ||
44 | result(NSNumber(value: 1)) | 58 | result(NSNumber(value: 1)) |
45 | } else if call.method == "directPrintPdf" { | 59 | } else if call.method == "directPrintPdf" { |
46 | - let name = args["name"] as? String ?? "" | ||
47 | - let printer = args["printer"] as? String | ||
48 | - let object = args["doc"] as? FlutterStandardTypedData | ||
49 | - directPrintPdf(name: name, data: object!.data, withPrinter: printer!) | ||
50 | - result(NSNumber(value: 1)) | ||
51 | - } else if call.method == "writePdf" { | ||
52 | - if let object = args["doc"] as? FlutterStandardTypedData { | ||
53 | - writePdf(object.data) | ||
54 | - } | ||
55 | - result(NSNumber(value: 1)) | ||
56 | - } else if call.method == "cancelJob" { | ||
57 | - renderer?.cancelJob() | ||
58 | - let controller = UIPrintInteractionController.shared | ||
59 | - controller.dismiss(animated: true) | 60 | + let name = args["name"] as! String |
61 | + let printer = args["printer"] as! String | ||
62 | + let object = args["doc"] as! FlutterStandardTypedData | ||
63 | + let printJob = PrintJob(printing: self, index: args["job"] as! Int) | ||
64 | + printJob.directPrintPdf(name: name, data: object.data, withPrinter: printer) | ||
60 | result(NSNumber(value: 1)) | 65 | result(NSNumber(value: 1)) |
61 | } else if call.method == "sharePdf" { | 66 | } else if call.method == "sharePdf" { |
62 | - if let object = args["doc"] as? FlutterStandardTypedData { | ||
63 | - sharePdf( | ||
64 | - object, | ||
65 | - withSourceRect: CGRect(x: CGFloat((args["x"] as? NSNumber)?.floatValue ?? 0.0), y: CGFloat((args["y"] as? NSNumber)?.floatValue ?? 0.0), width: CGFloat((args["w"] as? NSNumber)?.floatValue ?? 0.0), height: CGFloat((args["h"] as? NSNumber)?.floatValue ?? 0.0)), | ||
66 | - andName: args["name"] as? String | ||
67 | - ) | ||
68 | - } | 67 | + let object = args["doc"] as! FlutterStandardTypedData |
68 | + PrintJob.sharePdf( | ||
69 | + data: object.data, | ||
70 | + withSourceRect: CGRect( | ||
71 | + x: CGFloat((args["x"] as? NSNumber)?.floatValue ?? 0.0), | ||
72 | + y: CGFloat((args["y"] as? NSNumber)?.floatValue ?? 0.0), | ||
73 | + width: CGFloat((args["w"] as? NSNumber)?.floatValue ?? 0.0), | ||
74 | + height: CGFloat((args["h"] as? NSNumber)?.floatValue ?? 0.0) | ||
75 | + ), | ||
76 | + andName: args["name"] as! String | ||
77 | + ) | ||
69 | result(NSNumber(value: 1)) | 78 | result(NSNumber(value: 1)) |
70 | } else if call.method == "convertHtml" { | 79 | } else if call.method == "convertHtml" { |
71 | let width = CGFloat((args["width"] as? NSNumber)?.floatValue ?? 0.0) | 80 | let width = CGFloat((args["width"] as? NSNumber)?.floatValue ?? 0.0) |
@@ -74,8 +83,10 @@ public class PrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionControll | @@ -74,8 +83,10 @@ public class PrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionControll | ||
74 | let marginTop = CGFloat((args["marginTop"] as? NSNumber)?.floatValue ?? 0.0) | 83 | let marginTop = CGFloat((args["marginTop"] as? NSNumber)?.floatValue ?? 0.0) |
75 | let marginRight = CGFloat((args["marginRight"] as? NSNumber)?.floatValue ?? 0.0) | 84 | let marginRight = CGFloat((args["marginRight"] as? NSNumber)?.floatValue ?? 0.0) |
76 | let marginBottom = CGFloat((args["marginBottom"] as? NSNumber)?.floatValue ?? 0.0) | 85 | let marginBottom = CGFloat((args["marginBottom"] as? NSNumber)?.floatValue ?? 0.0) |
77 | - convertHtml( | ||
78 | - (args["html"] as? String)!, | 86 | + let printJob = PrintJob(printing: self, index: args["job"] as! Int) |
87 | + | ||
88 | + printJob.convertHtml( | ||
89 | + args["html"] as! String, | ||
79 | withPageSize: CGRect( | 90 | withPageSize: CGRect( |
80 | x: 0.0, | 91 | x: 0.0, |
81 | y: 0.0, | 92 | y: 0.0, |
@@ -88,219 +99,70 @@ public class PrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionControll | @@ -88,219 +99,70 @@ public class PrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionControll | ||
88 | width: width - marginRight - marginLeft, | 99 | width: width - marginRight - marginLeft, |
89 | height: height - marginBottom - marginTop | 100 | height: height - marginBottom - marginTop |
90 | ), | 101 | ), |
91 | - andBaseUrl: args["baseUrl"] as? String == nil ? nil : URL(string: (args["baseUrl"] as? String)!) | 102 | + andBaseUrl: args["baseUrl"] as? String == nil ? nil : URL(string: args["baseUrl"] as! String) |
92 | ) | 103 | ) |
93 | result(NSNumber(value: 1)) | 104 | result(NSNumber(value: 1)) |
94 | } else if call.method == "pickPrinter" { | 105 | } else if call.method == "pickPrinter" { |
95 | - pickPrinter(result, withSourceRect: CGRect( | 106 | + PrintJob.pickPrinter(result: result, withSourceRect: CGRect( |
96 | x: CGFloat((args["x"] as? NSNumber)?.floatValue ?? 0.0), | 107 | x: CGFloat((args["x"] as? NSNumber)?.floatValue ?? 0.0), |
97 | y: CGFloat((args["y"] as? NSNumber)?.floatValue ?? 0.0), | 108 | y: CGFloat((args["y"] as? NSNumber)?.floatValue ?? 0.0), |
98 | width: CGFloat((args["w"] as? NSNumber)?.floatValue ?? 0.0), | 109 | width: CGFloat((args["w"] as? NSNumber)?.floatValue ?? 0.0), |
99 | height: CGFloat((args["h"] as? NSNumber)?.floatValue ?? 0.0) | 110 | height: CGFloat((args["h"] as? NSNumber)?.floatValue ?? 0.0) |
100 | )) | 111 | )) |
101 | } else if call.method == "printingInfo" { | 112 | } else if call.method == "printingInfo" { |
102 | - let data: NSDictionary = [ | ||
103 | - "iosVersion": UIDevice.current.systemVersion, | ||
104 | - ] | ||
105 | - result(data) | 113 | + result(PrintJob.printingInfo()) |
106 | } else { | 114 | } else { |
107 | result(FlutterMethodNotImplemented) | 115 | result(FlutterMethodNotImplemented) |
108 | } | 116 | } |
109 | } | 117 | } |
110 | 118 | ||
111 | - func completionHandler(printController _: UIPrintInteractionController, completed: Bool, error: Error?) { | ||
112 | - if !completed, error != nil { | ||
113 | - print("Unable to print: \(error?.localizedDescription ?? "unknown error")") | ||
114 | - } | 119 | + /// Request the Pdf document from flutter |
120 | + public func onLayout(printJob: PrintJob, width: CGFloat, height: CGFloat, marginLeft: CGFloat, marginTop: CGFloat, marginRight: CGFloat, marginBottom: CGFloat) { | ||
121 | + let arg = [ | ||
122 | + "width": width, | ||
123 | + "height": height, | ||
124 | + "marginLeft": marginLeft, | ||
125 | + "marginTop": marginTop, | ||
126 | + "marginRight": marginRight, | ||
127 | + "marginBottom": marginBottom, | ||
128 | + "job": printJob.index, | ||
129 | + ] as [String: Any] | ||
130 | + | ||
131 | + channel.invokeMethod("onLayout", arguments: arg, result: { (result: Any?) -> Void in | ||
132 | + if result as? Bool == false { | ||
133 | + printJob.cancelJob() | ||
134 | + } else { | ||
135 | + let object = result as! FlutterStandardTypedData | ||
136 | + printJob.setDocument(object.data) | ||
137 | + } | ||
138 | + }) | ||
139 | + } | ||
115 | 140 | ||
141 | + /// send completion status to flutter | ||
142 | + public func onCompleted(printJob: PrintJob, completed: Bool, error: NSString?) { | ||
116 | let data: NSDictionary = [ | 143 | let data: NSDictionary = [ |
117 | "completed": completed, | 144 | "completed": completed, |
118 | - "error": error?.localizedDescription as Any, | 145 | + "error": error as Any, |
146 | + "job": printJob.index, | ||
119 | ] | 147 | ] |
120 | - channel?.invokeMethod("onCompleted", arguments: data) | ||
121 | - | ||
122 | - renderer = nil | ||
123 | - } | ||
124 | - | ||
125 | - func directPrintPdf(name: String, data: Data, withPrinter printerID: String) { | ||
126 | - let printing = UIPrintInteractionController.isPrintingAvailable | ||
127 | - if !printing { | ||
128 | - let data: NSDictionary = [ | ||
129 | - "completed": false, | ||
130 | - "error": "Printing not available", | ||
131 | - ] | ||
132 | - channel?.invokeMethod("onCompleted", arguments: data) | ||
133 | - return | ||
134 | - } | ||
135 | - | ||
136 | - let controller = UIPrintInteractionController.shared | ||
137 | - controller.delegate = self | ||
138 | - | ||
139 | - let printInfo = UIPrintInfo.printInfo() | ||
140 | - printInfo.jobName = name | ||
141 | - printInfo.outputType = .general | ||
142 | - controller.printInfo = printInfo | ||
143 | - controller.printingItem = data | ||
144 | - let printerURL = URL(string: printerID) | ||
145 | - | ||
146 | - if printerURL == nil { | ||
147 | - let data: NSDictionary = [ | ||
148 | - "completed": false, | ||
149 | - "error": "Unable to fine printer URL", | ||
150 | - ] | ||
151 | - channel?.invokeMethod("onCompleted", arguments: data) | ||
152 | - return | ||
153 | - } | ||
154 | - | ||
155 | - let printer = UIPrinter(url: printerURL!) | ||
156 | - controller.print(to: printer, completionHandler: completionHandler) | 148 | + channel.invokeMethod("onCompleted", arguments: data) |
157 | } | 149 | } |
158 | 150 | ||
159 | - func printPdf(_ name: String, data: Data?) { | ||
160 | - let printing = UIPrintInteractionController.isPrintingAvailable | ||
161 | - if !printing { | ||
162 | - let data: NSDictionary = [ | ||
163 | - "completed": false, | ||
164 | - "error": "Printing not available", | ||
165 | - ] | ||
166 | - channel?.invokeMethod("onCompleted", arguments: data) | ||
167 | - return | ||
168 | - } | ||
169 | - | ||
170 | - let controller = UIPrintInteractionController.shared | ||
171 | - controller.delegate = self | ||
172 | - | ||
173 | - let printInfo = UIPrintInfo.printInfo() | ||
174 | - printInfo.jobName = name | ||
175 | - printInfo.outputType = .general | ||
176 | - controller.printInfo = printInfo | ||
177 | - renderer = PdfPrintPageRenderer(channel, data: data) | ||
178 | - controller.printPageRenderer = renderer | ||
179 | - controller.present(animated: true, completionHandler: completionHandler) | ||
180 | - } | ||
181 | - | ||
182 | - func writePdf(_ data: Data) { | ||
183 | - renderer?.setDocument(data) | ||
184 | - } | ||
185 | - | ||
186 | - func sharePdf(_ data: FlutterStandardTypedData, withSourceRect rect: CGRect, andName name: String?) { | ||
187 | - let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) | ||
188 | - | ||
189 | - let uuid = CFUUIDCreate(nil) | ||
190 | - assert(uuid != nil) | ||
191 | - | ||
192 | - let uuidStr = CFUUIDCreateString(nil, uuid) | ||
193 | - assert(uuidStr != nil) | ||
194 | - | ||
195 | - var fileURL: URL | ||
196 | - if name == nil { | ||
197 | - fileURL = tmpDirURL.appendingPathComponent("document-\(uuidStr ?? "1" as CFString)").appendingPathExtension("pdf") | ||
198 | - } else { | ||
199 | - fileURL = tmpDirURL.appendingPathComponent(name!) | ||
200 | - } | ||
201 | - | ||
202 | - do { | ||
203 | - try data.data.write(to: fileURL, options: .atomic) | ||
204 | - } catch { | ||
205 | - print("sharePdf error: \(error.localizedDescription)") | ||
206 | - return | ||
207 | - } | ||
208 | - | ||
209 | - let activityViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) | ||
210 | - if UI_USER_INTERFACE_IDIOM() == .pad { | ||
211 | - let controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController | ||
212 | - activityViewController.popoverPresentationController?.sourceView = controller?.view | ||
213 | - activityViewController.popoverPresentationController?.sourceRect = rect | ||
214 | - } | ||
215 | - UIApplication.shared.keyWindow?.rootViewController?.present(activityViewController, animated: true) | ||
216 | - } | ||
217 | - | ||
218 | - func convertHtml(_ data: String, withPageSize rect: CGRect, andMargin margin: CGRect, andBaseUrl baseUrl: URL?) { | ||
219 | - let viewController = UIApplication.shared.delegate?.window?!.rootViewController | ||
220 | - let wkWebView = WKWebView(frame: viewController!.view.bounds) | ||
221 | - wkWebView.isHidden = true | ||
222 | - wkWebView.tag = 100 | ||
223 | - viewController?.view.addSubview(wkWebView) | ||
224 | - wkWebView.loadHTMLString(data, baseURL: baseUrl ?? Bundle.main.bundleURL) | ||
225 | - | ||
226 | - urlObservation = wkWebView.observe(\.isLoading, changeHandler: { _, _ in | ||
227 | - // this is workaround for issue with loading local images | ||
228 | - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { | ||
229 | - // assign the print formatter to the print page renderer | ||
230 | - let renderer = UIPrintPageRenderer() | ||
231 | - renderer.addPrintFormatter(wkWebView.viewPrintFormatter(), startingAtPageAt: 0) | ||
232 | - | ||
233 | - // assign paperRect and printableRect values | ||
234 | - renderer.setValue(rect, forKey: "paperRect") | ||
235 | - renderer.setValue(margin, forKey: "printableRect") | ||
236 | - | ||
237 | - // create pdf context and draw each page | ||
238 | - let pdfData = NSMutableData() | ||
239 | - UIGraphicsBeginPDFContextToData(pdfData, rect, nil) | ||
240 | - | ||
241 | - for i in 0 ..< renderer.numberOfPages { | ||
242 | - UIGraphicsBeginPDFPage() | ||
243 | - renderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds()) | ||
244 | - } | ||
245 | - | ||
246 | - UIGraphicsEndPDFContext() | ||
247 | - | ||
248 | - if let viewWithTag = viewController?.view.viewWithTag(wkWebView.tag) { | ||
249 | - viewWithTag.removeFromSuperview() // remove hidden webview when pdf is generated | ||
250 | - | ||
251 | - // clear WKWebView cache | ||
252 | - if #available(iOS 9.0, *) { | ||
253 | - WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in | ||
254 | - records.forEach { record in | ||
255 | - WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {}) | ||
256 | - } | ||
257 | - } | ||
258 | - } | ||
259 | - } | ||
260 | - | ||
261 | - // dispose urlObservation | ||
262 | - self.urlObservation = nil | ||
263 | - | ||
264 | - let data = FlutterStandardTypedData(bytes: pdfData as Data) | ||
265 | - self.channel?.invokeMethod("onHtmlRendered", arguments: data) | ||
266 | - } | ||
267 | - }) | 151 | + /// send html to pdf data result to flutter |
152 | + public func onHtmlRendered(printJob: PrintJob, pdfData: Data) { | ||
153 | + let data: NSDictionary = [ | ||
154 | + "doc": FlutterStandardTypedData(bytes: pdfData), | ||
155 | + "job": printJob.index, | ||
156 | + ] | ||
157 | + channel.invokeMethod("onHtmlRendered", arguments: data) | ||
268 | } | 158 | } |
269 | 159 | ||
270 | - func pickPrinter(_ result: @escaping FlutterResult, withSourceRect rect: CGRect) { | ||
271 | - let controller = UIPrinterPickerController(initiallySelectedPrinter: nil) | ||
272 | - | ||
273 | - let pickPrinterCompletionHandler: UIPrinterPickerController.CompletionHandler = { | ||
274 | - (printerPickerController: UIPrinterPickerController, completed: Bool, error: Error?) in | ||
275 | - if !completed, error != nil { | ||
276 | - print("Unable to pick printer: \(error?.localizedDescription ?? "unknown error")") | ||
277 | - result(nil) | ||
278 | - return | ||
279 | - } | ||
280 | - | ||
281 | - if printerPickerController.selectedPrinter == nil { | ||
282 | - result(nil) | ||
283 | - return | ||
284 | - } | ||
285 | - | ||
286 | - let printer = printerPickerController.selectedPrinter! | ||
287 | - let data: NSDictionary = [ | ||
288 | - "url": printer.url.absoluteString as Any, | ||
289 | - "name": printer.displayName as Any, | ||
290 | - "model": printer.makeAndModel as Any, | ||
291 | - "location": printer.displayLocation as Any, | ||
292 | - ] | ||
293 | - result(data) | ||
294 | - } | ||
295 | - | ||
296 | - if UI_USER_INTERFACE_IDIOM() == .pad { | ||
297 | - let viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController | ||
298 | - if viewController != nil { | ||
299 | - controller.present(from: rect, in: viewController!.view, animated: true, completionHandler: pickPrinterCompletionHandler) | ||
300 | - return | ||
301 | - } | ||
302 | - } | ||
303 | - | ||
304 | - controller.present(animated: true, completionHandler: pickPrinterCompletionHandler) | 160 | + /// send html to pdf conversion error to flutter |
161 | + public func onHtmlError(printJob: PrintJob, error: String) { | ||
162 | + let data: NSDictionary = [ | ||
163 | + "error": error, | ||
164 | + "job": printJob.index, | ||
165 | + ] | ||
166 | + channel.invokeMethod("onHtmlError", arguments: data) | ||
305 | } | 167 | } |
306 | } | 168 | } |
@@ -27,5 +27,8 @@ import 'package:pdf/pdf.dart'; | @@ -27,5 +27,8 @@ import 'package:pdf/pdf.dart'; | ||
27 | import 'package:pdf/widgets.dart'; | 27 | import 'package:pdf/widgets.dart'; |
28 | 28 | ||
29 | part 'src/asset_utils.dart'; | 29 | part 'src/asset_utils.dart'; |
30 | +part 'src/print_job.dart'; | ||
31 | +part 'src/printer.dart'; | ||
30 | part 'src/printing.dart'; | 32 | part 'src/printing.dart'; |
33 | +part 'src/printing_info.dart'; | ||
31 | part 'src/widgets.dart'; | 34 | part 'src/widgets.dart'; |
printing/lib/src/print_job.dart
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 | +part of printing; | ||
18 | + | ||
19 | +class _PrintJob { | ||
20 | + _PrintJob({ | ||
21 | + this.onLayout, | ||
22 | + this.onHtmlRendered, | ||
23 | + this.onCompleted, | ||
24 | + }); | ||
25 | + | ||
26 | + final LayoutCallback onLayout; | ||
27 | + final Completer<List<int>> onHtmlRendered; | ||
28 | + final Completer<bool> onCompleted; | ||
29 | + | ||
30 | + int index; | ||
31 | +} |
printing/lib/src/printer.dart
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 | +part of printing; | ||
18 | + | ||
19 | +@immutable | ||
20 | +class Printer { | ||
21 | + const Printer({ | ||
22 | + @required this.url, | ||
23 | + this.name, | ||
24 | + this.model, | ||
25 | + this.location, | ||
26 | + }) : assert(url != null); | ||
27 | + | ||
28 | + final String url; | ||
29 | + final String name; | ||
30 | + final String model; | ||
31 | + final String location; | ||
32 | + | ||
33 | + @override | ||
34 | + String toString() => name ?? url; | ||
35 | +} |
@@ -18,74 +18,65 @@ part of printing; | @@ -18,74 +18,65 @@ part of printing; | ||
18 | 18 | ||
19 | typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format); | 19 | typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format); |
20 | 20 | ||
21 | -@immutable | ||
22 | -class Printer { | ||
23 | - const Printer({ | ||
24 | - @required this.url, | ||
25 | - this.name, | ||
26 | - this.model, | ||
27 | - this.location, | ||
28 | - }) : assert(url != null); | ||
29 | - | ||
30 | - final String url; | ||
31 | - final String name; | ||
32 | - final String model; | ||
33 | - final String location; | ||
34 | - | ||
35 | - @override | ||
36 | - String toString() => name ?? url; | ||
37 | -} | ||
38 | - | ||
39 | mixin Printing { | 21 | mixin Printing { |
40 | static const MethodChannel _channel = MethodChannel('net.nfet.printing'); | 22 | static const MethodChannel _channel = MethodChannel('net.nfet.printing'); |
41 | - static LayoutCallback _onLayout; | ||
42 | - static Completer<List<int>> _onHtmlRendered; | ||
43 | - static Completer<bool> _onCompleted; | 23 | + static final Map<int, _PrintJob> _printJobs = <int, _PrintJob>{}; |
24 | + static int _jobIndex = 0; | ||
44 | 25 | ||
45 | /// Callbacks from platform plugins | 26 | /// Callbacks from platform plugins |
46 | - static Future<void> _handleMethod(MethodCall call) async { | 27 | + static Future<dynamic> _handleMethod(MethodCall call) async { |
47 | switch (call.method) { | 28 | switch (call.method) { |
48 | case 'onLayout': | 29 | case 'onLayout': |
30 | + final _PrintJob job = _printJobs[call.arguments['job']]; | ||
49 | try { | 31 | try { |
50 | - final List<int> bytes = await _onLayout(PdfPageFormat( | 32 | + final PdfPageFormat format = PdfPageFormat( |
51 | call.arguments['width'], | 33 | call.arguments['width'], |
52 | call.arguments['height'], | 34 | call.arguments['height'], |
53 | marginLeft: call.arguments['marginLeft'], | 35 | marginLeft: call.arguments['marginLeft'], |
54 | marginTop: call.arguments['marginTop'], | 36 | marginTop: call.arguments['marginTop'], |
55 | marginRight: call.arguments['marginRight'], | 37 | marginRight: call.arguments['marginRight'], |
56 | marginBottom: call.arguments['marginBottom'], | 38 | marginBottom: call.arguments['marginBottom'], |
57 | - )); | 39 | + ); |
40 | + | ||
41 | + final List<int> bytes = await job.onLayout(format); | ||
42 | + | ||
58 | if (bytes == null) { | 43 | if (bytes == null) { |
59 | - await _channel.invokeMethod<void>('cancelJob', <String, dynamic>{}); | ||
60 | - break; | 44 | + return false; |
61 | } | 45 | } |
62 | - final Map<String, dynamic> params = <String, dynamic>{ | ||
63 | - 'doc': Uint8List.fromList(bytes), | ||
64 | - }; | ||
65 | - await _channel.invokeMethod<void>('writePdf', params); | 46 | + |
47 | + return Uint8List.fromList(bytes); | ||
66 | } catch (e) { | 48 | } catch (e) { |
67 | print('Unable to print: $e'); | 49 | print('Unable to print: $e'); |
68 | - await _channel.invokeMethod<void>('cancelJob', <String, dynamic>{}); | 50 | + return false; |
69 | } | 51 | } |
70 | break; | 52 | break; |
71 | case 'onCompleted': | 53 | case 'onCompleted': |
72 | final bool completed = call.arguments['completed']; | 54 | final bool completed = call.arguments['completed']; |
73 | final String error = call.arguments['error']; | 55 | final String error = call.arguments['error']; |
56 | + final _PrintJob job = _printJobs[call.arguments['job']]; | ||
74 | if (completed == false && error != null) { | 57 | if (completed == false && error != null) { |
75 | - _onCompleted.completeError(error); | 58 | + job.onCompleted.completeError(error); |
76 | } else { | 59 | } else { |
77 | - _onCompleted.complete(completed); | 60 | + job.onCompleted.complete(completed); |
78 | } | 61 | } |
79 | break; | 62 | break; |
80 | case 'onHtmlRendered': | 63 | case 'onHtmlRendered': |
81 | - _onHtmlRendered.complete(call.arguments); | 64 | + final _PrintJob job = _printJobs[call.arguments['job']]; |
65 | + job.onHtmlRendered.complete(call.arguments['doc']); | ||
82 | break; | 66 | break; |
83 | case 'onHtmlError': | 67 | case 'onHtmlError': |
84 | - _onHtmlRendered.completeError(call.arguments); | 68 | + final _PrintJob job = _printJobs[call.arguments['job']]; |
69 | + job.onHtmlRendered.completeError(call.arguments['error']); | ||
85 | break; | 70 | break; |
86 | } | 71 | } |
87 | } | 72 | } |
88 | 73 | ||
74 | + static _PrintJob _newPrintJob(_PrintJob job) { | ||
75 | + job.index = _jobIndex++; | ||
76 | + _printJobs[job.index] = job; | ||
77 | + return job; | ||
78 | + } | ||
79 | + | ||
89 | /// Prints a Pdf document to a local printer using the platform UI | 80 | /// Prints a Pdf document to a local printer using the platform UI |
90 | /// the Pdf document is re-built in a [LayoutCallback] each time the | 81 | /// the Pdf document is re-built in a [LayoutCallback] each time the |
91 | /// user changes a setting like the page format or orientation. | 82 | /// user changes a setting like the page format or orientation. |
@@ -98,24 +89,33 @@ mixin Printing { | @@ -98,24 +89,33 @@ mixin Printing { | ||
98 | String name = 'Document', | 89 | String name = 'Document', |
99 | PdfPageFormat format = PdfPageFormat.standard, | 90 | PdfPageFormat format = PdfPageFormat.standard, |
100 | }) async { | 91 | }) async { |
101 | - _onCompleted = Completer<bool>(); | ||
102 | - _onLayout = onLayout; | ||
103 | _channel.setMethodCallHandler(_handleMethod); | 92 | _channel.setMethodCallHandler(_handleMethod); |
104 | - final Map<String, dynamic> params = <String, dynamic>{'name': name}; | 93 | + |
94 | + final _PrintJob job = _newPrintJob(_PrintJob( | ||
95 | + onCompleted: Completer<bool>(), | ||
96 | + onLayout: onLayout, | ||
97 | + )); | ||
98 | + | ||
99 | + final Map<String, dynamic> params = <String, dynamic>{ | ||
100 | + 'name': name, | ||
101 | + 'job': job.index, | ||
102 | + 'width': format.width, | ||
103 | + 'height': format.height, | ||
104 | + 'marginLeft': format.marginLeft, | ||
105 | + 'marginTop': format.marginTop, | ||
106 | + 'marginRight': format.marginRight, | ||
107 | + 'marginBottom': format.marginBottom, | ||
108 | + }; | ||
109 | + | ||
110 | + await _channel.invokeMethod<int>('printPdf', params); | ||
111 | + bool result = false; | ||
105 | try { | 112 | try { |
106 | - final Map<dynamic, dynamic> info = await printingInfo(); | ||
107 | - if (int.parse(info['iosVersion'].toString().split('.').first) >= 13) { | ||
108 | - final List<int> bytes = await onLayout(format); | ||
109 | - if (bytes == null) { | ||
110 | - return false; | ||
111 | - } | ||
112 | - params['doc'] = Uint8List.fromList(bytes); | ||
113 | - } | 113 | + result = await job.onCompleted.future; |
114 | } catch (e) { | 114 | } catch (e) { |
115 | - e.toString(); | 115 | + print('Document not printed: $e'); |
116 | } | 116 | } |
117 | - await _channel.invokeMethod<int>('printPdf', params); | ||
118 | - return _onCompleted.future; | 117 | + _printJobs.remove(job.index); |
118 | + return result; | ||
119 | } | 119 | } |
120 | 120 | ||
121 | static Future<Map<dynamic, dynamic>> printingInfo() async { | 121 | static Future<Map<dynamic, dynamic>> printingInfo() async { |
@@ -159,19 +159,31 @@ mixin Printing { | @@ -159,19 +159,31 @@ mixin Printing { | ||
159 | String name = 'Document', | 159 | String name = 'Document', |
160 | PdfPageFormat format = PdfPageFormat.standard, | 160 | PdfPageFormat format = PdfPageFormat.standard, |
161 | }) async { | 161 | }) async { |
162 | - _onCompleted = Completer<bool>(); | 162 | + if (printer == null) { |
163 | + return false; | ||
164 | + } | ||
165 | + | ||
163 | _channel.setMethodCallHandler(_handleMethod); | 166 | _channel.setMethodCallHandler(_handleMethod); |
167 | + | ||
168 | + final _PrintJob job = _newPrintJob(_PrintJob( | ||
169 | + onCompleted: Completer<bool>(), | ||
170 | + )); | ||
171 | + | ||
164 | final List<int> bytes = await onLayout(format); | 172 | final List<int> bytes = await onLayout(format); |
165 | if (bytes == null) { | 173 | if (bytes == null) { |
166 | return false; | 174 | return false; |
167 | } | 175 | } |
176 | + | ||
168 | final Map<String, dynamic> params = <String, dynamic>{ | 177 | final Map<String, dynamic> params = <String, dynamic>{ |
169 | 'name': name, | 178 | 'name': name, |
170 | 'printer': printer.url, | 179 | 'printer': printer.url, |
171 | 'doc': Uint8List.fromList(bytes), | 180 | 'doc': Uint8List.fromList(bytes), |
181 | + 'job': job.index, | ||
172 | }; | 182 | }; |
173 | await _channel.invokeMethod<int>('directPrintPdf', params); | 183 | await _channel.invokeMethod<int>('directPrintPdf', params); |
174 | - return _onCompleted.future; | 184 | + final bool result = await job.onCompleted.future; |
185 | + _printJobs.remove(job.index); | ||
186 | + return result; | ||
175 | } | 187 | } |
176 | 188 | ||
177 | /// Prints a [PdfDocument] or a pdf stream to a local printer using the platform UI | 189 | /// Prints a [PdfDocument] or a pdf stream to a local printer using the platform UI |
@@ -192,11 +204,12 @@ mixin Printing { | @@ -192,11 +204,12 @@ mixin Printing { | ||
192 | static Future<void> sharePdf({ | 204 | static Future<void> sharePdf({ |
193 | @Deprecated('use bytes with document.save()') PdfDocument document, | 205 | @Deprecated('use bytes with document.save()') PdfDocument document, |
194 | List<int> bytes, | 206 | List<int> bytes, |
195 | - String filename, | 207 | + String filename = 'document.pdf', |
196 | Rect bounds, | 208 | Rect bounds, |
197 | }) async { | 209 | }) async { |
198 | assert(document != null || bytes != null); | 210 | assert(document != null || bytes != null); |
199 | assert(!(document == null && bytes == null)); | 211 | assert(!(document == null && bytes == null)); |
212 | + assert(filename != null); | ||
200 | 213 | ||
201 | if (document != null) { | 214 | if (document != null) { |
202 | bytes = document.save(); | 215 | bytes = document.save(); |
@@ -220,6 +233,12 @@ mixin Printing { | @@ -220,6 +233,12 @@ mixin Printing { | ||
220 | {@required String html, | 233 | {@required String html, |
221 | String baseUrl, | 234 | String baseUrl, |
222 | PdfPageFormat format = PdfPageFormat.a4}) async { | 235 | PdfPageFormat format = PdfPageFormat.a4}) async { |
236 | + _channel.setMethodCallHandler(_handleMethod); | ||
237 | + | ||
238 | + final _PrintJob job = _newPrintJob(_PrintJob( | ||
239 | + onHtmlRendered: Completer<List<int>>(), | ||
240 | + )); | ||
241 | + | ||
223 | final Map<String, dynamic> params = <String, dynamic>{ | 242 | final Map<String, dynamic> params = <String, dynamic>{ |
224 | 'html': html, | 243 | 'html': html, |
225 | 'baseUrl': baseUrl, | 244 | 'baseUrl': baseUrl, |
@@ -229,11 +248,29 @@ mixin Printing { | @@ -229,11 +248,29 @@ mixin Printing { | ||
229 | 'marginTop': format.marginTop, | 248 | 'marginTop': format.marginTop, |
230 | 'marginRight': format.marginRight, | 249 | 'marginRight': format.marginRight, |
231 | 'marginBottom': format.marginBottom, | 250 | 'marginBottom': format.marginBottom, |
251 | + 'job': job.index, | ||
232 | }; | 252 | }; |
233 | 253 | ||
234 | - _channel.setMethodCallHandler(_handleMethod); | ||
235 | - _onHtmlRendered = Completer<List<int>>(); | ||
236 | await _channel.invokeMethod<void>('convertHtml', params); | 254 | await _channel.invokeMethod<void>('convertHtml', params); |
237 | - return _onHtmlRendered.future; | 255 | + final List<int> result = await job.onHtmlRendered.future; |
256 | + _printJobs.remove(job.index); | ||
257 | + return result; | ||
258 | + } | ||
259 | + | ||
260 | + static Future<PrintingInfo> info() async { | ||
261 | + _channel.setMethodCallHandler(_handleMethod); | ||
262 | + Map<dynamic, dynamic> result; | ||
263 | + | ||
264 | + try { | ||
265 | + result = await _channel.invokeMethod( | ||
266 | + 'printingInfo', | ||
267 | + <String, dynamic>{}, | ||
268 | + ); | ||
269 | + } catch (e) { | ||
270 | + print('Error getting printing info: $e'); | ||
271 | + return PrintingInfo.unavailable; | ||
272 | + } | ||
273 | + | ||
274 | + return PrintingInfo.fromMap(result); | ||
238 | } | 275 | } |
239 | } | 276 | } |
printing/lib/src/printing_info.dart
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 | +part of printing; | ||
18 | + | ||
19 | +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 | + ); | ||
27 | + | ||
28 | + const PrintingInfo._({ | ||
29 | + this.directPrint = false, | ||
30 | + this.dynamicLayout = false, | ||
31 | + this.canPrint = false, | ||
32 | + this.canConvertHtml = false, | ||
33 | + this.canShare = false, | ||
34 | + }) : assert(directPrint != null), | ||
35 | + assert(dynamicLayout != null), | ||
36 | + assert(canPrint != null), | ||
37 | + assert(canConvertHtml != null), | ||
38 | + assert(canShare != null); | ||
39 | + | ||
40 | + static const PrintingInfo unavailable = PrintingInfo._(); | ||
41 | + | ||
42 | + final bool directPrint; | ||
43 | + final bool dynamicLayout; | ||
44 | + final bool canPrint; | ||
45 | + final bool canConvertHtml; | ||
46 | + final bool canShare; | ||
47 | +} |
-
Please register or login to post a comment