David PHAM-VAN

Add dynamicLayout setting to prevent iOS to freeze

@@ -27,8 +27,10 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -27,8 +27,10 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
27 private var pdfDocument: CGPDFDocument? 27 private var pdfDocument: CGPDFDocument?
28 private var urlObservation: NSKeyValueObservation? 28 private var urlObservation: NSKeyValueObservation?
29 private var jobName: String? 29 private var jobName: String?
  30 + private var printerName: String?
30 private var orientation: UIPrintInfo.Orientation? 31 private var orientation: UIPrintInfo.Orientation?
31 private let semaphore = DispatchSemaphore(value: 0) 32 private let semaphore = DispatchSemaphore(value: 0)
  33 + private var dynamic = false
32 34
33 public init(printing: PrintingPlugin, index: Int) { 35 public init(printing: PrintingPlugin, index: Int) {
34 self.printing = printing 36 self.printing = printing
@@ -47,9 +49,13 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -47,9 +49,13 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
47 } 49 }
48 } 50 }
49 51
50 - func cancelJob(_: String?) { 52 + func cancelJob(_ error: String?) {
51 pdfDocument = nil 53 pdfDocument = nil
52 - semaphore.signal() 54 + if dynamic {
  55 + semaphore.signal()
  56 + } else {
  57 + printing.onCompleted(printJob: self, completed: false, error: error as NSString?)
  58 + }
53 } 59 }
54 60
55 func setDocument(_ data: Data?) { 61 func setDocument(_ data: Data?) {
@@ -58,23 +64,57 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -58,23 +64,57 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
58 let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback) 64 let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback)
59 pdfDocument = CGPDFDocument(dataProvider!) 65 pdfDocument = CGPDFDocument(dataProvider!)
60 66
61 - // Unblock the main thread  
62 - semaphore.signal() 67 + if dynamic {
  68 + // Unblock the main thread
  69 + semaphore.signal()
  70 + return
  71 + }
  72 +
  73 + let controller = UIPrintInteractionController.shared
  74 + controller.delegate = self
  75 +
  76 + let printInfo = UIPrintInfo.printInfo()
  77 + printInfo.jobName = jobName!
  78 + printInfo.outputType = .general
  79 + if orientation != nil {
  80 + printInfo.orientation = orientation!
  81 + orientation = nil
  82 + }
  83 + controller.printInfo = printInfo
  84 + controller.printPageRenderer = self
  85 +
  86 + DispatchQueue.main.async {
  87 + if self.printerName != nil {
  88 + let printerURL = URL(string: self.printerName!)
  89 +
  90 + if printerURL == nil {
  91 + self.printing.onCompleted(printJob: self, completed: false, error: "Unable to find printer URL")
  92 + return
  93 + }
  94 +
  95 + let printer = UIPrinter(url: printerURL!)
  96 + controller.print(to: printer, completionHandler: self.completionHandler)
  97 + } else {
  98 + controller.present(animated: true, completionHandler: self.completionHandler)
  99 + }
  100 + }
63 } 101 }
64 102
65 override public var numberOfPages: Int { 103 override public var numberOfPages: Int {
66 - printing.onLayout(  
67 - printJob: self,  
68 - width: paperRect.size.width,  
69 - height: paperRect.size.height,  
70 - marginLeft: printableRect.origin.x,  
71 - marginTop: printableRect.origin.y,  
72 - marginRight: paperRect.size.width - (printableRect.origin.x + printableRect.size.width),  
73 - marginBottom: paperRect.size.height - (printableRect.origin.y + printableRect.size.height)  
74 - )  
75 -  
76 - // Block the main thread, waiting for a document  
77 - semaphore.wait() 104 + if dynamic {
  105 + printing.onLayout(
  106 + printJob: self,
  107 + width: paperRect.size.width,
  108 + height: paperRect.size.height,
  109 + marginLeft: printableRect.origin.x,
  110 + marginTop: printableRect.origin.y,
  111 + marginRight: paperRect.size.width - (printableRect.origin.x + printableRect.size.width),
  112 + marginBottom: paperRect.size.height - (printableRect.origin.y + printableRect.size.height)
  113 + )
  114 +
  115 + // Block the main thread, waiting for a document
  116 + semaphore.wait()
  117 + }
78 118
79 return pdfDocument?.numberOfPages ?? 0 119 return pdfDocument?.numberOfPages ?? 0
80 } 120 }
@@ -87,7 +127,8 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -87,7 +127,8 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
87 printing.onCompleted(printJob: self, completed: completed, error: error?.localizedDescription as NSString?) 127 printing.onCompleted(printJob: self, completed: completed, error: error?.localizedDescription as NSString?)
88 } 128 }
89 129
90 - func printPdf(name: String, withPageSize size: CGSize, andMargin _: CGRect, withPrinter printerID: String?) { 130 + func printPdf(name: String, withPageSize size: CGSize, andMargin margin: CGRect, withPrinter printerID: String?, dynamically dyn: Bool) {
  131 + dynamic = dyn
91 let printing = UIPrintInteractionController.isPrintingAvailable 132 let printing = UIPrintInteractionController.isPrintingAvailable
92 if !printing { 133 if !printing {
93 self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available") 134 self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available")
@@ -99,6 +140,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -99,6 +140,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
99 } 140 }
100 141
101 jobName = name 142 jobName = name
  143 + printerName = printerID
102 144
103 let controller = UIPrintInteractionController.shared 145 let controller = UIPrintInteractionController.shared
104 controller.delegate = self 146 controller.delegate = self
@@ -128,7 +170,20 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate @@ -128,7 +170,20 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
128 return 170 return
129 } 171 }
130 172
131 - controller.present(animated: true, completionHandler: completionHandler) 173 + if dynamic {
  174 + controller.present(animated: true, completionHandler: completionHandler)
  175 + return
  176 + }
  177 +
  178 + self.printing.onLayout(
  179 + printJob: self,
  180 + width: size.width,
  181 + height: size.height,
  182 + marginLeft: margin.minX,
  183 + marginTop: margin.minY,
  184 + marginRight: size.width - margin.maxX,
  185 + marginBottom: size.height - margin.maxY
  186 + )
132 } 187 }
133 188
134 static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) { 189 static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) {
@@ -59,6 +59,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -59,6 +59,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
59 let marginRight = CGFloat((args["marginRight"] as! NSNumber).floatValue) 59 let marginRight = CGFloat((args["marginRight"] as! NSNumber).floatValue)
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 jobs[args["job"] as! UInt32] = printJob 63 jobs[args["job"] as! UInt32] = printJob
63 printJob.printPdf(name: name, 64 printJob.printPdf(name: name,
64 withPageSize: CGSize( 65 withPageSize: CGSize(
@@ -70,7 +71,8 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -70,7 +71,8 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
70 y: marginTop, 71 y: marginTop,
71 width: width - marginRight - marginLeft, 72 width: width - marginRight - marginLeft,
72 height: height - marginBottom - marginTop 73 height: height - marginBottom - marginTop
73 - ), withPrinter: printer) 74 + ), withPrinter: printer,
  75 + dynamically: dynamic)
74 result(NSNumber(value: 1)) 76 result(NSNumber(value: 1))
75 } else if call.method == "sharePdf" { 77 } else if call.method == "sharePdf" {
76 let object = args["doc"] as! FlutterStandardTypedData 78 let object = args["doc"] as! FlutterStandardTypedData
@@ -64,6 +64,7 @@ abstract class PrintingPlatform extends PlatformInterface { @@ -64,6 +64,7 @@ abstract class PrintingPlatform extends PlatformInterface {
64 LayoutCallback onLayout, 64 LayoutCallback onLayout,
65 String name, 65 String name,
66 PdfPageFormat format, 66 PdfPageFormat format,
  67 + bool dynamicLayout,
67 ); 68 );
68 69
69 /// Enumerate the available printers on the system. 70 /// Enumerate the available printers on the system.
@@ -142,6 +142,7 @@ class MethodChannelPrinting extends PrintingPlatform { @@ -142,6 +142,7 @@ class MethodChannelPrinting extends PrintingPlatform {
142 LayoutCallback onLayout, 142 LayoutCallback onLayout,
143 String name, 143 String name,
144 PdfPageFormat format, 144 PdfPageFormat format,
  145 + bool dynamicLayout,
145 ) async { 146 ) async {
146 final job = _printJobs.add( 147 final job = _printJobs.add(
147 onCompleted: Completer<bool>(), 148 onCompleted: Completer<bool>(),
@@ -158,6 +159,7 @@ class MethodChannelPrinting extends PrintingPlatform { @@ -158,6 +159,7 @@ class MethodChannelPrinting extends PrintingPlatform {
158 'marginTop': format.marginTop, 159 'marginTop': format.marginTop,
159 'marginRight': format.marginRight, 160 'marginRight': format.marginRight,
160 'marginBottom': format.marginBottom, 161 'marginBottom': format.marginBottom,
  162 + 'dynamic': dynamicLayout,
161 }; 163 };
162 164
163 await _channel.invokeMethod<int>('printPdf', params); 165 await _channel.invokeMethod<int>('printPdf', params);
@@ -34,6 +34,7 @@ class PdfPreview extends StatefulWidget { @@ -34,6 +34,7 @@ class PdfPreview extends StatefulWidget {
34 this.pdfFileName, 34 this.pdfFileName,
35 this.useActions = true, 35 this.useActions = true,
36 this.pages, 36 this.pages,
  37 + this.dynamicLayout = true,
37 }) : super(key: key); 38 }) : super(key: key);
38 39
39 /// Called when a pdf document is needed 40 /// Called when a pdf document is needed
@@ -90,6 +91,11 @@ class PdfPreview extends StatefulWidget { @@ -90,6 +91,11 @@ class PdfPreview extends StatefulWidget {
90 /// Pages to display. Default will display all the pages. 91 /// Pages to display. Default will display all the pages.
91 final List<int>? pages; 92 final List<int>? pages;
92 93
  94 + /// Request page re-layout to match the printer paper and margins.
  95 + /// Mitigate an issue with iOS and macOS print dialog that prevent any
  96 + /// channel message while opened.
  97 + final bool dynamicLayout;
  98 +
93 @override 99 @override
94 _PdfPreviewState createState() => _PdfPreviewState(); 100 _PdfPreviewState createState() => _PdfPreviewState();
95 } 101 }
@@ -512,6 +518,7 @@ class _PdfPreviewState extends State<PdfPreview> { @@ -512,6 +518,7 @@ class _PdfPreviewState extends State<PdfPreview> {
512 onLayout: widget.build, 518 onLayout: widget.build,
513 name: widget.pdfFileName ?? 'Document', 519 name: widget.pdfFileName ?? 'Document',
514 format: format, 520 format: format,
  521 + dynamicLayout: widget.dynamicLayout,
515 ); 522 );
516 523
517 if (result && widget.onPrinted != null) { 524 if (result && widget.onPrinted != null) {
@@ -40,12 +40,14 @@ mixin Printing { @@ -40,12 +40,14 @@ mixin Printing {
40 required LayoutCallback onLayout, 40 required LayoutCallback onLayout,
41 String name = 'Document', 41 String name = 'Document',
42 PdfPageFormat format = PdfPageFormat.standard, 42 PdfPageFormat format = PdfPageFormat.standard,
  43 + bool dynamicLayout = true,
43 }) { 44 }) {
44 return PrintingPlatform.instance.layoutPdf( 45 return PrintingPlatform.instance.layoutPdf(
45 null, 46 null,
46 onLayout, 47 onLayout,
47 name, 48 name,
48 format, 49 format,
  50 + dynamicLayout,
49 ); 51 );
50 } 52 }
51 53
@@ -122,12 +124,14 @@ mixin Printing { @@ -122,12 +124,14 @@ mixin Printing {
122 required LayoutCallback onLayout, 124 required LayoutCallback onLayout,
123 String name = 'Document', 125 String name = 'Document',
124 PdfPageFormat format = PdfPageFormat.standard, 126 PdfPageFormat format = PdfPageFormat.standard,
  127 + bool dynamicLayout = true,
125 }) { 128 }) {
126 return PrintingPlatform.instance.layoutPdf( 129 return PrintingPlatform.instance.layoutPdf(
127 printer, 130 printer,
128 onLayout, 131 onLayout,
129 name, 132 name,
130 format, 133 format,
  134 + dynamicLayout,
131 ); 135 );
132 } 136 }
133 137
@@ -30,6 +30,7 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -30,6 +30,7 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
30 private var pdfDocument: CGPDFDocument? 30 private var pdfDocument: CGPDFDocument?
31 private var page: CGPDFPage? 31 private var page: CGPDFPage?
32 private let semaphore = DispatchSemaphore(value: 0) 32 private let semaphore = DispatchSemaphore(value: 0)
  33 + private var dynamic = false
33 34
34 public init(printing: PrintingPlugin, index: Int) { 35 public init(printing: PrintingPlugin, index: Int) {
35 self.printing = printing 36 self.printing = printing
@@ -49,18 +50,20 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -49,18 +50,20 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
49 setFrameSize(size) 50 setFrameSize(size)
50 setBoundsSize(size) 51 setBoundsSize(size)
51 52
52 - printing.onLayout(  
53 - printJob: self,  
54 - width: printOperation!.printInfo.paperSize.width,  
55 - height: printOperation!.printInfo.paperSize.height,  
56 - marginLeft: printOperation!.printInfo.leftMargin,  
57 - marginTop: printOperation!.printInfo.topMargin,  
58 - marginRight: printOperation!.printInfo.rightMargin,  
59 - marginBottom: printOperation!.printInfo.bottomMargin  
60 - )  
61 -  
62 - // Block the main thread, waiting for a document  
63 - semaphore.wait() 53 + if dynamic {
  54 + printing.onLayout(
  55 + printJob: self,
  56 + width: printOperation!.printInfo.paperSize.width,
  57 + height: printOperation!.printInfo.paperSize.height,
  58 + marginLeft: printOperation!.printInfo.leftMargin,
  59 + marginTop: printOperation!.printInfo.topMargin,
  60 + marginRight: printOperation!.printInfo.rightMargin,
  61 + marginBottom: printOperation!.printInfo.bottomMargin
  62 + )
  63 +
  64 + // Block the main thread, waiting for a document
  65 + semaphore.wait()
  66 + }
64 67
65 if pdfDocument != nil { 68 if pdfDocument != nil {
66 range.pointee.length = pdfDocument!.numberOfPages 69 range.pointee.length = pdfDocument!.numberOfPages
@@ -90,8 +93,16 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -90,8 +93,16 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
90 let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback) 93 let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback)
91 pdfDocument = CGPDFDocument(dataProvider!) 94 pdfDocument = CGPDFDocument(dataProvider!)
92 95
93 - // Unblock the main thread  
94 - semaphore.signal() 96 + if dynamic {
  97 + // Unblock the main thread
  98 + semaphore.signal()
  99 + return
  100 + }
  101 +
  102 + DispatchQueue.main.async {
  103 + let window = NSApplication.shared.mainWindow!
  104 + self.printOperation!.runModal(for: window, delegate: self, didRun: #selector(self.printOperationDidRun(printOperation:success:contextInfo:)), contextInfo: nil)
  105 + }
95 } 106 }
96 107
97 override public func draw(_: NSRect) { 108 override public func draw(_: NSRect) {
@@ -119,7 +130,8 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -119,7 +130,8 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
119 return printers 130 return printers
120 } 131 }
121 132
122 - public func printPdf(name: String, withPageSize size: CGSize, andMargin _: CGRect, withPrinter printer: String?) { 133 + public func printPdf(name: String, withPageSize size: CGSize, andMargin _: CGRect, withPrinter printer: String?, dynamically dyn: Bool) {
  134 + dynamic = dyn
123 let sharedInfo = NSPrintInfo.shared 135 let sharedInfo = NSPrintInfo.shared
124 let sharedDict = sharedInfo.dictionary() 136 let sharedDict = sharedInfo.dictionary()
125 let printInfoDict = NSMutableDictionary(dictionary: sharedDict) 137 let printInfoDict = NSMutableDictionary(dictionary: sharedDict)
@@ -133,20 +145,38 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate { @@ -133,20 +145,38 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
133 // Print the custom view 145 // Print the custom view
134 printOperation = NSPrintOperation(view: self, printInfo: printInfo) 146 printOperation = NSPrintOperation(view: self, printInfo: printInfo)
135 printOperation!.jobTitle = name 147 printOperation!.jobTitle = name
136 - printOperation!.printPanel.options = [.showsPreview, .showsPaperSize, .showsOrientation] 148 + printOperation!.printPanel.options = [.showsPreview]
137 if printer != nil { 149 if printer != nil {
138 printInfo.printer = NSPrinter(name: printer!)! 150 printInfo.printer = NSPrinter(name: printer!)!
139 printOperation!.showsPrintPanel = false 151 printOperation!.showsPrintPanel = false
140 printOperation!.showsProgressPanel = false 152 printOperation!.showsProgressPanel = false
141 } 153 }
142 154
143 - let window = NSApplication.shared.mainWindow!  
144 - printOperation!.runModal(for: window, delegate: self, didRun: #selector(printOperationDidRun(printOperation:success:contextInfo:)), contextInfo: nil) 155 + if dynamic {
  156 + let window = NSApplication.shared.mainWindow!
  157 + printOperation!.printPanel.options = [.showsPreview, .showsPaperSize, .showsOrientation]
  158 + printOperation!.runModal(for: window, delegate: self, didRun: #selector(printOperationDidRun(printOperation:success:contextInfo:)), contextInfo: nil)
  159 + return
  160 + }
  161 +
  162 + printing.onLayout(
  163 + printJob: self,
  164 + width: printOperation!.printInfo.paperSize.width,
  165 + height: printOperation!.printInfo.paperSize.height,
  166 + marginLeft: printOperation!.printInfo.leftMargin,
  167 + marginTop: printOperation!.printInfo.topMargin,
  168 + marginRight: printOperation!.printInfo.rightMargin,
  169 + marginBottom: printOperation!.printInfo.bottomMargin
  170 + )
145 } 171 }
146 172
147 - func cancelJob(_: String?) { 173 + func cancelJob(_ error: String?) {
148 pdfDocument = nil 174 pdfDocument = nil
149 - semaphore.signal() 175 + if dynamic {
  176 + semaphore.signal()
  177 + } else {
  178 + printing.onCompleted(printJob: self, completed: false, error: error as NSString?)
  179 + }
150 } 180 }
151 181
152 public static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) { 182 public static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) {
@@ -59,6 +59,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -59,6 +59,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
59 let marginRight = CGFloat((args["marginRight"] as! NSNumber).floatValue) 59 let marginRight = CGFloat((args["marginRight"] as! NSNumber).floatValue)
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 jobs[args["job"] as! UInt32] = printJob 63 jobs[args["job"] as! UInt32] = printJob
63 printJob.printPdf(name: name, 64 printJob.printPdf(name: name,
64 withPageSize: CGSize( 65 withPageSize: CGSize(
@@ -70,7 +71,8 @@ public class PrintingPlugin: NSObject, FlutterPlugin { @@ -70,7 +71,8 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
70 y: marginTop, 71 y: marginTop,
71 width: width - marginRight - marginLeft, 72 width: width - marginRight - marginLeft,
72 height: height - marginBottom - marginTop 73 height: height - marginBottom - marginTop
73 - ), withPrinter: printer) 74 + ), withPrinter: printer,
  75 + dynamically: dynamic)
74 result(NSNumber(value: 1)) 76 result(NSNumber(value: 1))
75 } else if call.method == "listPrinters" { 77 } else if call.method == "listPrinters" {
76 let printJob = PrintJob(printing: self, index: -1) 78 let printJob = PrintJob(printing: self, index: -1)
@@ -113,6 +113,7 @@ class MockPrinting extends Mock @@ -113,6 +113,7 @@ class MockPrinting extends Mock
113 LayoutCallback onLayout, 113 LayoutCallback onLayout,
114 String name, 114 String name,
115 PdfPageFormat format, 115 PdfPageFormat format,
  116 + bool dynamicLayout,
116 ) async => 117 ) async =>
117 true; 118 true;
118 119