@capgo/capacitor-printer 8.1.1 → 8.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -14,6 +14,7 @@ import android.print.PageRange;
|
|
|
14
14
|
import android.print.PrintAttributes;
|
|
15
15
|
import android.print.PrintDocumentAdapter;
|
|
16
16
|
import android.print.PrintDocumentInfo;
|
|
17
|
+
import android.print.PrintJob;
|
|
17
18
|
import android.print.PrintManager;
|
|
18
19
|
import android.util.Base64;
|
|
19
20
|
import android.webkit.WebView;
|
|
@@ -26,9 +27,26 @@ import java.io.FileOutputStream;
|
|
|
26
27
|
import java.io.IOException;
|
|
27
28
|
import java.io.InputStream;
|
|
28
29
|
import java.io.OutputStream;
|
|
30
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
31
|
+
import java.util.concurrent.atomic.AtomicReference;
|
|
29
32
|
|
|
30
33
|
public class Printer {
|
|
31
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Reports the end of a print session back to the caller.
|
|
37
|
+
*/
|
|
38
|
+
public interface OnPrintFinishCallback {
|
|
39
|
+
/**
|
|
40
|
+
* Invoked once the print session has ended, whether the user printed or cancelled.
|
|
41
|
+
*/
|
|
42
|
+
void onFinish();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Invoked when the job could not be started at all.
|
|
46
|
+
*/
|
|
47
|
+
void onError(String message);
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
private final Context context;
|
|
33
51
|
private final Activity activity;
|
|
34
52
|
|
|
@@ -168,6 +186,19 @@ public class Printer {
|
|
|
168
186
|
}
|
|
169
187
|
|
|
170
188
|
public void printWebView(WebView webView, String name) throws Exception {
|
|
189
|
+
printWebView(webView, name, null);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Prints the current content of the web view.
|
|
194
|
+
*
|
|
195
|
+
* <p>The adapter returned by {@link WebView#createPrintDocumentAdapter(String)} renders lazily
|
|
196
|
+
* from the live web view, so callers must keep that content in place until {@code callback}
|
|
197
|
+
* reports the session has finished.
|
|
198
|
+
*
|
|
199
|
+
* @param callback notified once the print session ends, or null for fire-and-forget printing.
|
|
200
|
+
*/
|
|
201
|
+
public void printWebView(WebView webView, String name, OnPrintFinishCallback callback) throws Exception {
|
|
171
202
|
if (webView == null) {
|
|
172
203
|
throw new Exception("WebView not available");
|
|
173
204
|
}
|
|
@@ -175,7 +206,15 @@ public class Printer {
|
|
|
175
206
|
new Runnable() {
|
|
176
207
|
@Override
|
|
177
208
|
public void run() {
|
|
178
|
-
|
|
209
|
+
try {
|
|
210
|
+
createWebPrintJob(webView, name, callback);
|
|
211
|
+
} catch (Exception e) {
|
|
212
|
+
// The job is created asynchronously on the UI thread, so failures here
|
|
213
|
+
// cannot surface through the throws clause above.
|
|
214
|
+
if (callback != null) {
|
|
215
|
+
callback.onError(e.getMessage());
|
|
216
|
+
}
|
|
217
|
+
}
|
|
179
218
|
}
|
|
180
219
|
}
|
|
181
220
|
);
|
|
@@ -229,13 +268,60 @@ public class Printer {
|
|
|
229
268
|
}
|
|
230
269
|
|
|
231
270
|
private void createWebPrintJob(WebView webView, String name) {
|
|
271
|
+
try {
|
|
272
|
+
createWebPrintJob(webView, name, null);
|
|
273
|
+
} catch (Exception e) {
|
|
274
|
+
// Preserves the previous silent behaviour for callers without a callback.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private void createWebPrintJob(WebView webView, String name, OnPrintFinishCallback callback) throws Exception {
|
|
232
279
|
PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
|
|
233
280
|
if (printManager == null) {
|
|
234
|
-
|
|
281
|
+
throw new Exception("Print service not available");
|
|
235
282
|
}
|
|
236
283
|
|
|
237
284
|
PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(name);
|
|
238
|
-
|
|
285
|
+
|
|
286
|
+
if (callback == null) {
|
|
287
|
+
printManager.print(name, printAdapter, new PrintAttributes.Builder().build());
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Populated right after print() returns. We are on the UI thread here and the adapter
|
|
292
|
+
// callbacks are dispatched to that same thread, so none of them can run before the
|
|
293
|
+
// reference is set.
|
|
294
|
+
AtomicReference<PrintJob> printJob = new AtomicReference<>();
|
|
295
|
+
|
|
296
|
+
// The framework calls onFinish() on the wrapped adapter once the print session ends,
|
|
297
|
+
// which is the earliest point at which the web view content is no longer needed.
|
|
298
|
+
PrintDocumentAdapter proxy = new PrintFinishAdapter(printAdapter, () -> {
|
|
299
|
+
PrintJob job = printJob.get();
|
|
300
|
+
|
|
301
|
+
// Layout and write failures are reported to the framework's own result callbacks,
|
|
302
|
+
// which cannot be intercepted — PrintDocumentAdapter.LayoutResultCallback and
|
|
303
|
+
// WriteResultCallback are not subclassable outside android.print. The resulting job
|
|
304
|
+
// state is the only signal available to us, and it is best effort: a job that fails
|
|
305
|
+
// after the session ends (offline printer) settles too late to be seen here.
|
|
306
|
+
if (job != null && job.isFailed()) {
|
|
307
|
+
callback.onError("Print job failed");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
callback.onFinish();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
PrintJob job = printManager.print(name, proxy, new PrintAttributes.Builder().build());
|
|
315
|
+
|
|
316
|
+
// print() is documented to return null on failure (despite its @NonNull annotation) and
|
|
317
|
+
// does so when the print feature is missing or the print dialog cannot be started. The
|
|
318
|
+
// adapter is never invoked in that case, so onFinish() would never arrive.
|
|
319
|
+
if (job == null) {
|
|
320
|
+
callback.onError("Failed to start print job");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
printJob.set(job);
|
|
239
325
|
}
|
|
240
326
|
|
|
241
327
|
private File saveTempFile(byte[] data, String mimeType) throws IOException {
|
|
@@ -282,6 +368,61 @@ public class Printer {
|
|
|
282
368
|
}
|
|
283
369
|
}
|
|
284
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Delegating adapter that additionally notifies when the print session has finished.
|
|
373
|
+
*/
|
|
374
|
+
private static class PrintFinishAdapter extends PrintDocumentAdapter {
|
|
375
|
+
|
|
376
|
+
private final PrintDocumentAdapter delegate;
|
|
377
|
+
private final Runnable onFinished;
|
|
378
|
+
private final AtomicBoolean notified = new AtomicBoolean(false);
|
|
379
|
+
|
|
380
|
+
PrintFinishAdapter(@NonNull PrintDocumentAdapter delegate, @NonNull Runnable onFinished) {
|
|
381
|
+
this.delegate = delegate;
|
|
382
|
+
this.onFinished = onFinished;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@Override
|
|
386
|
+
public void onStart() {
|
|
387
|
+
delegate.onStart();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@Override
|
|
391
|
+
public void onLayout(
|
|
392
|
+
PrintAttributes oldAttributes,
|
|
393
|
+
PrintAttributes newAttributes,
|
|
394
|
+
CancellationSignal cancellationSignal,
|
|
395
|
+
LayoutResultCallback callback,
|
|
396
|
+
Bundle extras
|
|
397
|
+
) {
|
|
398
|
+
delegate.onLayout(oldAttributes, newAttributes, cancellationSignal, callback, extras);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@Override
|
|
402
|
+
public void onWrite(
|
|
403
|
+
PageRange[] pages,
|
|
404
|
+
ParcelFileDescriptor destination,
|
|
405
|
+
CancellationSignal cancellationSignal,
|
|
406
|
+
WriteResultCallback callback
|
|
407
|
+
) {
|
|
408
|
+
delegate.onWrite(pages, destination, cancellationSignal, callback);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@Override
|
|
412
|
+
public void onFinish() {
|
|
413
|
+
try {
|
|
414
|
+
// Let the WebView adapter release its own resources first.
|
|
415
|
+
delegate.onFinish();
|
|
416
|
+
} finally {
|
|
417
|
+
// Runs even if the delegate throws during teardown, so the caller is always
|
|
418
|
+
// notified exactly once.
|
|
419
|
+
if (notified.compareAndSet(false, true)) {
|
|
420
|
+
onFinished.run();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
285
426
|
// Custom PrintDocumentAdapter for PDF files
|
|
286
427
|
private class PdfDocumentAdapter extends PrintDocumentAdapter {
|
|
287
428
|
|
|
@@ -9,7 +9,7 @@ import com.getcapacitor.annotation.CapacitorPlugin;
|
|
|
9
9
|
@CapacitorPlugin(name = "Printer")
|
|
10
10
|
public class PrinterPlugin extends Plugin {
|
|
11
11
|
|
|
12
|
-
private final String pluginVersion = "8.1.
|
|
12
|
+
private final String pluginVersion = "8.1.3";
|
|
13
13
|
|
|
14
14
|
private Printer implementation;
|
|
15
15
|
|
|
@@ -120,8 +120,25 @@ public class PrinterPlugin extends Plugin {
|
|
|
120
120
|
String name = call.getString("name", "Document");
|
|
121
121
|
|
|
122
122
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
// Resolve once the print session ends rather than once the job is handed to the
|
|
124
|
+
// system: the WebView still backs the print adapter until then, so resolving early
|
|
125
|
+
// lets callers navigate away while pages are still being rendered.
|
|
126
|
+
implementation.printWebView(
|
|
127
|
+
getBridge().getWebView(),
|
|
128
|
+
name,
|
|
129
|
+
new Printer.OnPrintFinishCallback() {
|
|
130
|
+
@Override
|
|
131
|
+
public void onFinish() {
|
|
132
|
+
// A cancelled job is a normal outcome, not a failure.
|
|
133
|
+
call.resolve();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Override
|
|
137
|
+
public void onError(String message) {
|
|
138
|
+
call.reject("Failed to print web view: " + message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
125
142
|
} catch (Exception e) {
|
|
126
143
|
call.reject("Failed to print web view: " + e.getMessage(), e);
|
|
127
144
|
}
|
|
@@ -10,6 +10,7 @@ import WebKit
|
|
|
10
10
|
case unsupportedMimeType
|
|
11
11
|
case printingNotAvailable
|
|
12
12
|
case invalidData
|
|
13
|
+
case presentationFailed
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/// Print base64 encoded data
|
|
@@ -175,10 +176,15 @@ import WebKit
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
/// Print web view content
|
|
179
|
+
///
|
|
180
|
+
/// `completion` is invoked once the print interaction has been dismissed, whether the user
|
|
181
|
+
/// printed or cancelled. `viewPrintFormatter()` renders lazily from the live web view, so
|
|
182
|
+
/// callers must keep that content in place until `completion` fires.
|
|
178
183
|
public func printWebView(
|
|
179
184
|
webView: WKWebView,
|
|
180
185
|
name: String,
|
|
181
|
-
presentingViewController: UIViewController
|
|
186
|
+
presentingViewController: UIViewController?,
|
|
187
|
+
completion: ((Bool, Error?) -> Void)? = nil
|
|
182
188
|
) throws {
|
|
183
189
|
let printInfo = UIPrintInfo(dictionary: nil)
|
|
184
190
|
printInfo.jobName = name
|
|
@@ -190,7 +196,8 @@ import WebKit
|
|
|
190
196
|
printInfo: printInfo,
|
|
191
197
|
printFormatter: formatter,
|
|
192
198
|
printItem: nil,
|
|
193
|
-
presentingViewController: presentingViewController
|
|
199
|
+
presentingViewController: presentingViewController,
|
|
200
|
+
completion: completion
|
|
194
201
|
)
|
|
195
202
|
}
|
|
196
203
|
|
|
@@ -200,7 +207,8 @@ import WebKit
|
|
|
200
207
|
printInfo: UIPrintInfo,
|
|
201
208
|
printFormatter: UIPrintFormatter?,
|
|
202
209
|
printItem: Any?,
|
|
203
|
-
presentingViewController: UIViewController
|
|
210
|
+
presentingViewController: UIViewController?,
|
|
211
|
+
completion: ((Bool, Error?) -> Void)? = nil
|
|
204
212
|
) throws {
|
|
205
213
|
guard UIPrintInteractionController.isPrintingAvailable else {
|
|
206
214
|
throw PrinterError.printingNotAvailable
|
|
@@ -219,21 +227,41 @@ import WebKit
|
|
|
219
227
|
printController.printingItem = item
|
|
220
228
|
}
|
|
221
229
|
|
|
230
|
+
// Only build a handler when a caller asked for one, so callers that opted out keep the
|
|
231
|
+
// existing fire-and-forget behaviour.
|
|
232
|
+
var handler: UIPrintInteractionController.CompletionHandler?
|
|
233
|
+
|
|
234
|
+
if let completion = completion {
|
|
235
|
+
handler = { _, completed, error in
|
|
236
|
+
completion(completed, error)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
222
240
|
// Present print controller
|
|
241
|
+
let presented: Bool
|
|
242
|
+
|
|
223
243
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
|
224
244
|
// For iPad, present as popover
|
|
225
|
-
printController.present(
|
|
245
|
+
presented = printController.present(
|
|
226
246
|
from: viewController.view.bounds,
|
|
227
247
|
in: viewController.view,
|
|
228
248
|
animated: true,
|
|
229
|
-
completionHandler:
|
|
249
|
+
completionHandler: handler
|
|
230
250
|
)
|
|
231
251
|
} else {
|
|
232
252
|
// For iPhone, present modally
|
|
233
|
-
printController.present(
|
|
253
|
+
presented = printController.present(
|
|
234
254
|
animated: true,
|
|
235
|
-
completionHandler:
|
|
255
|
+
completionHandler: handler
|
|
236
256
|
)
|
|
237
257
|
}
|
|
258
|
+
|
|
259
|
+
// A false return means nothing was shown and the completion handler will never be
|
|
260
|
+
// scheduled — for example when another print interaction is already presented, since
|
|
261
|
+
// UIPrintInteractionController.shared is a singleton. Callers waiting on that handler
|
|
262
|
+
// would otherwise never hear back.
|
|
263
|
+
if !presented {
|
|
264
|
+
throw PrinterError.presentationFailed
|
|
265
|
+
}
|
|
238
266
|
}
|
|
239
267
|
}
|
|
@@ -3,7 +3,7 @@ import Capacitor
|
|
|
3
3
|
|
|
4
4
|
@objc(PrinterPlugin)
|
|
5
5
|
public class PrinterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
-
private let pluginVersion: String = "8.1.
|
|
6
|
+
private let pluginVersion: String = "8.1.3"
|
|
7
7
|
public let identifier = "PrinterPlugin"
|
|
8
8
|
public let jsName = "Printer"
|
|
9
9
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -159,12 +159,21 @@ public class PrinterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
do {
|
|
162
|
+
// Resolve from the print interaction's completion handler rather than straight
|
|
163
|
+
// after presenting: the web view still backs the print formatter until the sheet
|
|
164
|
+
// is dismissed, so resolving early lets callers navigate away mid-render.
|
|
162
165
|
try self.implementation.printWebView(
|
|
163
166
|
webView: webView,
|
|
164
167
|
name: name,
|
|
165
168
|
presentingViewController: self.bridge?.viewController
|
|
166
|
-
)
|
|
167
|
-
|
|
169
|
+
) { _, error in
|
|
170
|
+
if let error = error {
|
|
171
|
+
call.reject("Failed to print web view: \(error.localizedDescription)")
|
|
172
|
+
} else {
|
|
173
|
+
// A user-cancelled sheet is a normal outcome, not a failure.
|
|
174
|
+
call.resolve()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
168
177
|
} catch {
|
|
169
178
|
call.reject("Failed to print web view: \(error.localizedDescription)")
|
|
170
179
|
}
|
package/package.json
CHANGED