Matteo Ricupero
Committed by David PHAM-VAN

Workaround for iOS bug + force paper size:

implemented fix when UIPrinter isn't contactable even if accessible due to an iOS bug.

added a flag to force using custom paper size to use when the combination of airprint+printer driver choose a wrong paper format.
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 ## 5.13.2 3 ## 5.13.2
4 4
5 - Added new printing output type value on iOS [Matteo Ricupero] 5 - Added new printing output type value on iOS [Matteo Ricupero]
  6 +- Workaround for iOS bug and force paper size [Matteo Ricupero]
6 7
7 ## 5.13.1 8 ## 5.13.1
8 9
  1 +public class CustomPrintPaper: UIPrintPaper {
  2 + private let size: CGSize
  3 +
  4 + override public var paperSize: CGSize { return size }
  5 + override public var printableRect: CGRect { return CGRect(origin: CGPoint.zero, size: size) }
  6 +
  7 + init(size: CGSize) {
  8 + self.size = size
  9 + }
  10 +}
@@ -25,6 +25,9 @@ func dataProviderReleaseDataCallback(info _: UnsafeMutableRawPointer?, data: Uns @@ -25,6 +25,9 @@ func dataProviderReleaseDataCallback(info _: UnsafeMutableRawPointer?, data: Uns
25 // Each printer will be identified by its URL string 25 // Each printer will be identified by its URL string
26 var selectedPrinters = [String: UIPrinter]() 26 var selectedPrinters = [String: UIPrinter]()
27 27
  28 +// Holds the printer after it was picked
  29 +var pickedPrinter: UIPrinter?
  30 +
28 public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate { 31 public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate {
29 private var printing: PrintingPlugin 32 private var printing: PrintingPlugin
30 public var index: Int 33 public var index: Int
@@ -36,6 +39,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -36,6 +39,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
36 private let semaphore = DispatchSemaphore(value: 0) 39 private let semaphore = DispatchSemaphore(value: 0)
37 private var dynamic = false 40 private var dynamic = false
38 private var currentSize: CGSize? 41 private var currentSize: CGSize?
  42 + private var forceCustomPrintPaper = false
39 43
40 public init(printing: PrintingPlugin, index: Int) { 44 public init(printing: PrintingPlugin, index: Int) {
41 self.printing = printing 45 self.printing = printing
@@ -149,6 +153,10 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -149,6 +153,10 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
149 return paperList[0] 153 return paperList[0]
150 } 154 }
151 155
  156 + if forceCustomPrintPaper {
  157 + return CustomPrintPaper(size: currentSize!)
  158 + }
  159 +
152 for paper in paperList { 160 for paper in paperList {
153 if (paper.paperSize.width == currentSize!.width && paper.paperSize.height == currentSize!.height) || 161 if (paper.paperSize.width == currentSize!.width && paper.paperSize.height == currentSize!.height) ||
154 (paper.paperSize.width == currentSize!.height && paper.paperSize.height == currentSize!.width) 162 (paper.paperSize.width == currentSize!.height && paper.paperSize.height == currentSize!.width)
@@ -162,9 +170,11 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -162,9 +170,11 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
162 return bestPaper 170 return bestPaper
163 } 171 }
164 172
165 - func printPdf(name: String, withPageSize size: CGSize, andMargin margin: CGRect, withPrinter printerID: String?, dynamically dyn: Bool, outputType type: UIPrintInfo.OutputType) { 173 + func printPdf(name: String, withPageSize size: CGSize, andMargin margin: CGRect, withPrinter printerID: String?, dynamically dyn: Bool, outputType type: UIPrintInfo.OutputType, forceCustomPrintPaper: Bool = false) {
166 currentSize = size 174 currentSize = size
167 dynamic = dyn 175 dynamic = dyn
  176 + self.forceCustomPrintPaper = forceCustomPrintPaper
  177 +
168 let printing = UIPrintInteractionController.isPrintingAvailable 178 let printing = UIPrintInteractionController.isPrintingAvailable
169 if !printing { 179 if !printing {
170 self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available") 180 self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available")
@@ -207,6 +217,14 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -207,6 +217,14 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
207 selectedPrinters[printerURLString] = UIPrinter(url: printerURL!) 217 selectedPrinters[printerURLString] = UIPrinter(url: printerURL!)
208 } 218 }
209 219
  220 + // Sometimes using UIPrinter(url:) gives a non-contactable printer.
  221 + // https://stackoverflow.com/questions/34602302/creating-a-working-uiprinter-object-from-url-for-dialogue-free-printing
  222 + // This lets use a printer saved during picking and fall back using a printer created with UIPrinter(url:)
  223 + if pickedPrinter != nil && selectedPrinters[printerURLString]!.url == pickedPrinter?.url {
  224 + controller.print(to: pickedPrinter!, completionHandler: completionHandler)
  225 + return
  226 + }
  227 +
210 selectedPrinters[printerURLString]!.contactPrinter { available in 228 selectedPrinters[printerURLString]!.contactPrinter { available in
211 if !available { 229 if !available {
212 self.printing.onCompleted(printJob: self, completed: false, error: "Printer not available") 230 self.printing.onCompleted(printJob: self, completed: false, error: "Printer not available")
@@ -328,6 +346,9 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -328,6 +346,9 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
328 "model": printer.makeAndModel as Any, 346 "model": printer.makeAndModel as Any,
329 "location": printer.displayLocation as Any, 347 "location": printer.displayLocation as Any,
330 ] 348 ]
  349 +
  350 + pickedPrinter = printer
  351 +
331 result(data) 352 result(data)
332 } 353 }
333 354
@@ -60,6 +60,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -60,6 +60,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
60 let marginBottom = CGFloat((args["marginBottom"] as! NSNumber).floatValue) 60 let marginBottom = CGFloat((args["marginBottom"] as! NSNumber).floatValue)
61 let printJob = PrintJob(printing: self, index: args["job"] as! Int) 61 let printJob = PrintJob(printing: self, index: args["job"] as! Int)
62 let dynamic = args["dynamic"] as! Bool 62 let dynamic = args["dynamic"] as! Bool
  63 + let forceCustomPrintPaper = args["forceCustomPrintPaper"] as! Bool
63 64
64 let outputType: UIPrintInfo.OutputType 65 let outputType: UIPrintInfo.OutputType
65 switch args["outputType"] as! Int { 66 switch args["outputType"] as! Int {
@@ -89,7 +90,8 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -89,7 +90,8 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
89 ), 90 ),
90 withPrinter: printer, 91 withPrinter: printer,
91 dynamically: dynamic, 92 dynamically: dynamic,
92 - outputType: outputType) 93 + outputType: outputType,
  94 + forceCustomPrintPaper: forceCustomPrintPaper)
93 result(NSNumber(value: 1)) 95 result(NSNumber(value: 1))
94 } else if call.method == "sharePdf" { 96 } else if call.method == "sharePdf" {
95 let object = args["doc"] as! FlutterStandardTypedData 97 let object = args["doc"] as! FlutterStandardTypedData
@@ -158,6 +158,7 @@ class PrintingPlugin extends PrintingPlatform { @@ -158,6 +158,7 @@ class PrintingPlugin extends PrintingPlatform {
158 bool dynamicLayout, 158 bool dynamicLayout,
159 bool usePrinterSettings, 159 bool usePrinterSettings,
160 OutputType outputType, 160 OutputType outputType,
  161 + bool forceCustomPrintPaper,
161 ) async { 162 ) async {
162 late Uint8List result; 163 late Uint8List result;
163 try { 164 try {
@@ -68,6 +68,7 @@ abstract class PrintingPlatform extends PlatformInterface { @@ -68,6 +68,7 @@ abstract class PrintingPlatform extends PlatformInterface {
68 bool dynamicLayout, 68 bool dynamicLayout,
69 bool usePrinterSettings, 69 bool usePrinterSettings,
70 OutputType outputType, 70 OutputType outputType,
  71 + bool forceCustomPrintPaper,
71 ); 72 );
72 73
73 /// Enumerate the available printers on the system. 74 /// Enumerate the available printers on the system.
@@ -182,6 +182,7 @@ class MethodChannelPrinting extends PrintingPlatform { @@ -182,6 +182,7 @@ class MethodChannelPrinting extends PrintingPlatform {
182 bool dynamicLayout, 182 bool dynamicLayout,
183 bool usePrinterSettings, 183 bool usePrinterSettings,
184 OutputType outputType, 184 OutputType outputType,
  185 + bool forceCustomPrintPaper,
185 ) async { 186 ) async {
186 final job = _printJobs.add( 187 final job = _printJobs.add(
187 onCompleted: Completer<bool>(), 188 onCompleted: Completer<bool>(),
@@ -201,6 +202,7 @@ class MethodChannelPrinting extends PrintingPlatform { @@ -201,6 +202,7 @@ class MethodChannelPrinting extends PrintingPlatform {
201 'dynamic': dynamicLayout, 202 'dynamic': dynamicLayout,
202 'usePrinterSettings': usePrinterSettings, 203 'usePrinterSettings': usePrinterSettings,
203 'outputType': outputType.index, 204 'outputType': outputType.index,
  205 + 'forceCustomPrintPaper': forceCustomPrintPaper,
204 }; 206 };
205 207
206 await _channel.invokeMethod<int>('printPdf', params); 208 await _channel.invokeMethod<int>('printPdf', params);
@@ -40,9 +40,15 @@ mixin Printing { @@ -40,9 +40,15 @@ mixin Printing {
40 /// Set [usePrinterSettings] to true to use the configuration defined by 40 /// Set [usePrinterSettings] to true to use the configuration defined by
41 /// the printer. May not work for all the printers and can depend on the 41 /// the printer. May not work for all the printers and can depend on the
42 /// drivers. (Supported platforms: Windows) 42 /// drivers. (Supported platforms: Windows)
  43 + ///
43 /// Set [outputType] to [OutputType.generic] to use the default printing 44 /// Set [outputType] to [OutputType.generic] to use the default printing
44 /// system, or [OutputType.photos] to use the photo printing system. 45 /// system, or [OutputType.photos] to use the photo printing system.
45 /// (Supported platforms: iOS) 46 /// (Supported platforms: iOS)
  47 + ///
  48 + /// Use [customPrintPaper] to force the printer to use a custom paper size.
  49 + /// Use value `true` to use [format] as custom paper size, when the printer
  50 + /// driver will not allows the user to use papers which are actually supported by the printer.
  51 + /// (Supported platforms: iOS)
46 static Future<bool> layoutPdf({ 52 static Future<bool> layoutPdf({
47 required LayoutCallback onLayout, 53 required LayoutCallback onLayout,
48 String name = 'Document', 54 String name = 'Document',
@@ -50,6 +56,7 @@ mixin Printing { @@ -50,6 +56,7 @@ mixin Printing {
50 bool dynamicLayout = true, 56 bool dynamicLayout = true,
51 bool usePrinterSettings = false, 57 bool usePrinterSettings = false,
52 OutputType outputType = OutputType.generic, 58 OutputType outputType = OutputType.generic,
  59 + bool forceCustomPrintPaper = false,
53 }) { 60 }) {
54 return PrintingPlatform.instance.layoutPdf( 61 return PrintingPlatform.instance.layoutPdf(
55 null, 62 null,
@@ -59,6 +66,7 @@ mixin Printing { @@ -59,6 +66,7 @@ mixin Printing {
59 dynamicLayout, 66 dynamicLayout,
60 usePrinterSettings, 67 usePrinterSettings,
61 outputType, 68 outputType,
  69 + forceCustomPrintPaper,
62 ); 70 );
63 } 71 }
64 72
@@ -138,6 +146,15 @@ mixin Printing { @@ -138,6 +146,15 @@ mixin Printing {
138 /// Set [usePrinterSettings] to true to use the configuration defined by 146 /// Set [usePrinterSettings] to true to use the configuration defined by
139 /// the printer. May not work for all the printers and can depend on the 147 /// the printer. May not work for all the printers and can depend on the
140 /// drivers. (Supported platforms: Windows) 148 /// drivers. (Supported platforms: Windows)
  149 + ///
  150 + /// Set [outputType] to [OutputType.generic] to use the default printing
  151 + /// system, or [OutputType.photos] to use the photo printing system.
  152 + /// (Supported platforms: iOS)
  153 + ///
  154 + /// Use [customPrintPaper] to force the printer to use a custom paper size.
  155 + /// Use value `true` to use [format] as custom paper size, when the printer
  156 + /// driver will not allows the user to use papers which are actually supported by the printer.
  157 + /// (Supported platforms: iOS)
141 static FutureOr<bool> directPrintPdf({ 158 static FutureOr<bool> directPrintPdf({
142 required Printer printer, 159 required Printer printer,
143 required LayoutCallback onLayout, 160 required LayoutCallback onLayout,
@@ -146,6 +163,7 @@ mixin Printing { @@ -146,6 +163,7 @@ mixin Printing {
146 bool dynamicLayout = true, 163 bool dynamicLayout = true,
147 bool usePrinterSettings = false, 164 bool usePrinterSettings = false,
148 OutputType outputType = OutputType.generic, 165 OutputType outputType = OutputType.generic,
  166 + bool forceCustomPrintPaper = false,
149 }) { 167 }) {
150 return PrintingPlatform.instance.layoutPdf( 168 return PrintingPlatform.instance.layoutPdf(
151 printer, 169 printer,
@@ -155,6 +173,7 @@ mixin Printing { @@ -155,6 +173,7 @@ mixin Printing {
155 dynamicLayout, 173 dynamicLayout,
156 usePrinterSettings, 174 usePrinterSettings,
157 outputType, 175 outputType,
  176 + forceCustomPrintPaper,
158 ); 177 );
159 } 178 }
160 179
@@ -109,6 +109,7 @@ class MockPrinting extends Mock @@ -109,6 +109,7 @@ class MockPrinting extends Mock
109 bool dynamicLayout, 109 bool dynamicLayout,
110 bool usePrinterSettings, 110 bool usePrinterSettings,
111 OutputType outputType, 111 OutputType outputType,
  112 + bool forceCustomPrintPaper,
112 ) async => 113 ) async =>
113 true; 114 true;
114 115