David PHAM-VAN

Implement dynamic layout on iOS and macOS

... ... @@ -5,6 +5,7 @@
- Add imageFromAssetBundle and networkImage
- Add Page orientation on PdfPreview
- Improve PrintJob object
- Implement dynamic layout on iOS and macOS
## 5.0.0-nullsafety.1
... ...
... ... @@ -28,6 +28,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
private var urlObservation: NSKeyValueObservation?
private var jobName: String?
private var orientation: UIPrintInfo.Orientation?
private let semaphore = DispatchSemaphore(value: 0)
public init(printing: PrintingPlugin, index: Int) {
self.printing = printing
... ... @@ -46,9 +47,9 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
}
}
func cancelJob(_ error: String?) {
func cancelJob(_: String?) {
pdfDocument = nil
printing.onCompleted(printJob: self, completed: false, error: error as NSString?)
semaphore.signal()
}
func setDocument(_ data: Data?) {
... ... @@ -57,24 +58,25 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback)
pdfDocument = CGPDFDocument(dataProvider!)
let controller = UIPrintInteractionController.shared
controller.delegate = self
let printInfo = UIPrintInfo.printInfo()
printInfo.jobName = jobName!
printInfo.outputType = .general
if orientation != nil {
printInfo.orientation = orientation!
orientation = nil
}
controller.printInfo = printInfo
controller.printPageRenderer = self
controller.present(animated: true, completionHandler: completionHandler)
// Unblock the main thread
semaphore.signal()
}
override public var numberOfPages: Int {
let pages = pdfDocument?.numberOfPages ?? 0
return pages
printing.onLayout(
printJob: self,
width: paperRect.size.width,
height: paperRect.size.height,
marginLeft: printableRect.origin.x,
marginTop: printableRect.origin.y,
marginRight: paperRect.size.width - (printableRect.origin.x + printableRect.size.width),
marginBottom: paperRect.size.height - (printableRect.origin.y + printableRect.size.height)
)
// Block the main thread, waiting for a document
semaphore.wait()
return pdfDocument?.numberOfPages ?? 0
}
func completionHandler(printController _: UIPrintInteractionController, completed: Bool, error: Error?) {
... ... @@ -110,7 +112,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
controller.print(to: printer, completionHandler: completionHandler)
}
func printPdf(name: String, withPageSize size: CGSize, andMargin margin: CGRect) {
func printPdf(name: String, withPageSize size: CGSize, andMargin _: CGRect) {
let printing = UIPrintInteractionController.isPrintingAvailable
if !printing {
self.printing.onCompleted(printJob: self, completed: false, error: "Printing not available")
... ... @@ -123,15 +125,21 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
jobName = name
self.printing.onLayout(
printJob: self,
width: size.width,
height: size.height,
marginLeft: margin.minX,
marginTop: margin.minY,
marginRight: size.width - margin.maxX,
marginBottom: size.height - margin.maxY
)
let controller = UIPrintInteractionController.shared
controller.delegate = self
let printInfo = UIPrintInfo.printInfo()
printInfo.jobName = jobName!
printInfo.outputType = .general
if orientation != nil {
printInfo.orientation = orientation!
orientation = nil
}
controller.printInfo = printInfo
controller.showsPaperSelectionForLoadedPapers = true
controller.printPageRenderer = self
controller.present(animated: true, completionHandler: completionHandler)
}
static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) {
... ... @@ -287,7 +295,7 @@ public class PrintJob: UIPrintPageRenderer, UIPrintInteractionControllerDelegate
public static func printingInfo() -> NSDictionary {
let data: NSDictionary = [
"directPrint": true,
"dynamicLayout": false,
"dynamicLayout": true,
"canPrint": true,
"canConvertHtml": true,
"canShare": true,
... ...
... ... @@ -17,12 +17,29 @@
import Flutter
import Foundation
// Dart:ffi API
private var _printingPlugin: PrintingPlugin?
@_cdecl("net_nfet_printing_set_document")
func setDocument(job: UInt32, doc: UnsafePointer<UInt8>, size: UInt64) {
_printingPlugin!.jobs[job]?.setDocument(Data(bytes: doc, count: Int(size)))
}
@_cdecl("net_nfet_printing_set_error")
func setError(job: UInt32, message: UnsafePointer<CChar>) {
_printingPlugin!.jobs[job]?.cancelJob(String(cString: message))
}
// End of Dart:ffi API
public class PrintingPlugin: NSObject, FlutterPlugin {
private var channel: FlutterMethodChannel
public var jobs = [UInt32: PrintJob]()
init(_ channel: FlutterMethodChannel) {
self.channel = channel
super.init()
_printingPlugin = self
}
/// Entry point
... ... @@ -44,6 +61,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
let marginRight = CGFloat((args["marginRight"] as? NSNumber)?.floatValue ?? 0.0)
let marginBottom = CGFloat((args["marginBottom"] as? NSNumber)?.floatValue ?? 0.0)
let printJob = PrintJob(printing: self, index: args["job"] as! Int)
jobs[args["job"] as! UInt32] = printJob
printJob.printPdf(name: name,
withPageSize: CGSize(
width: width,
... ... @@ -137,19 +155,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
"job": printJob.index,
] as [String: Any]
channel.invokeMethod("onLayout", arguments: arg, result: { (result: Any?) -> Void in
if result as? Bool == false {
printJob.cancelJob(nil)
} else if result is FlutterError {
let error = result as! FlutterError
printJob.cancelJob(error.message)
} else if result is FlutterStandardTypedData {
let object = result as! FlutterStandardTypedData
printJob.setDocument(object.data)
} else {
printJob.cancelJob("Unknown data type")
}
})
channel.invokeMethod("onLayout", arguments: arg)
}
/// send completion status to flutter
... ... @@ -160,6 +166,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
"job": printJob.index,
]
channel.invokeMethod("onCompleted", arguments: data)
jobs.removeValue(forKey: UInt32(printJob.index))
}
/// send html to pdf data result to flutter
... ...
... ... @@ -15,11 +15,13 @@
*/
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/rendering.dart' show Rect;
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:printing/src/method_channel_ffi.dart';
import 'callback.dart';
import 'interface.dart';
... ... @@ -56,7 +58,20 @@ class MethodChannelPrinting extends PrintingPlatform {
marginBottom: call.arguments['marginBottom'],
);
final bytes = await job.onLayout!(format);
Uint8List bytes;
try {
bytes = await job.onLayout!(format);
} catch (e) {
if (Platform.isMacOS || Platform.isIOS) {
return setErrorFfi(job, e.toString());
}
rethrow;
}
if (Platform.isMacOS || Platform.isIOS) {
return setDocumentFfi(job, bytes);
}
return Uint8List.fromList(bytes);
case 'onCompleted':
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:ffi' as ffi;
import 'dart:io' as io;
import 'dart:typed_data';
import 'package:ffi/ffi.dart' as ffi;
import 'print_job.dart';
/// Load the dynamic library
final ffi.DynamicLibrary _dynamicLibrary = _open();
ffi.DynamicLibrary _open() {
if (io.Platform.isMacOS || io.Platform.isIOS) {
return ffi.DynamicLibrary.process();
}
throw UnsupportedError('This platform is not supported.');
}
/// Set the Pdf document data
void setDocumentFfi(PrintJob job, Uint8List data) {
final nativeBytes = ffi.allocate<ffi.Uint8>(count: data.length);
nativeBytes.asTypedList(data.length).setAll(0, data);
_setDocument(job.index, nativeBytes, data.length);
}
final _SetDocument_Dart _setDocument =
_dynamicLibrary.lookupFunction<_SetDocument_C, _SetDocument_Dart>(
'net_nfet_printing_set_document',
);
typedef _SetDocument_C = ffi.Void Function(
ffi.Uint32 job,
ffi.Pointer<ffi.Uint8> data,
ffi.Uint64 size,
);
typedef _SetDocument_Dart = void Function(
int job,
ffi.Pointer<ffi.Uint8> data,
int size,
);
/// Set the Pdf Error message
void setErrorFfi(PrintJob job, String message) {
_setError(job.index, ffi.Utf8.toUtf8(message));
}
final _SetError_Dart _setError =
_dynamicLibrary.lookupFunction<_SetError_C, _SetError_Dart>(
'net_nfet_printing_set_error',
);
typedef _SetError_C = ffi.Void Function(
ffi.Uint32 job,
ffi.Pointer<ffi.Utf8> message,
);
typedef _SetError_Dart = void Function(
int job,
ffi.Pointer<ffi.Utf8> message,
);
... ...
... ... @@ -29,6 +29,7 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
private var printOperation: NSPrintOperation?
private var pdfDocument: CGPDFDocument?
private var page: CGPDFPage?
private let semaphore = DispatchSemaphore(value: 0)
public init(printing: PrintingPlugin, index: Int) {
self.printing = printing
... ... @@ -47,7 +48,29 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
setFrameSize(size)
setBoundsSize(size)
range.pointee.length = pdfDocument?.numberOfPages ?? 0
printing.onLayout(
printJob: self,
width: printOperation!.printInfo.paperSize.width,
height: printOperation!.printInfo.paperSize.height,
marginLeft: printOperation!.printInfo.leftMargin,
marginTop: printOperation!.printInfo.topMargin,
marginRight: printOperation!.printInfo.rightMargin,
marginBottom: printOperation!.printInfo.bottomMargin
)
// Block the main thread, waiting for a document
semaphore.wait()
if pdfDocument != nil {
range.pointee.length = pdfDocument!.numberOfPages
let page = pdfDocument!.page(at: 1)
let size = page?.getBoxRect(CGPDFBox.mediaBox) ?? NSZeroRect
setFrameSize(size.size)
setBoundsSize(size.size)
} else {
range.pointee.length = 0
}
return true
}
... ... @@ -67,8 +90,8 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
let dataProvider = CGDataProvider(dataInfo: nil, data: bytesPointer, size: data?.count ?? 0, releaseData: dataProviderReleaseDataCallback)
pdfDocument = CGPDFDocument(dataProvider!)
let window = NSApplication.shared.mainWindow!
printOperation!.runModal(for: window, delegate: self, didRun: #selector(printOperationDidRun(printOperation:success:contextInfo:)), contextInfo: nil)
// Unblock the main thread
semaphore.signal()
}
override public func draw(_: NSRect) {
... ... @@ -136,21 +159,15 @@ public class PrintJob: NSView, NSSharingServicePickerDelegate {
// Print the custom view
printOperation = NSPrintOperation(view: self, printInfo: printInfo)
printOperation!.jobTitle = name
printOperation!.printPanel.options = [.showsPreview]
printOperation!.printPanel.options = [.showsPreview, .showsPaperSize, .showsOrientation]
printing.onLayout(
printJob: self,
width: printOperation!.printInfo.paperSize.width,
height: printOperation!.printInfo.paperSize.height,
marginLeft: printOperation!.printInfo.leftMargin,
marginTop: printOperation!.printInfo.topMargin,
marginRight: printOperation!.printInfo.rightMargin,
marginBottom: printOperation!.printInfo.bottomMargin
)
let window = NSApplication.shared.mainWindow!
printOperation!.runModal(for: window, delegate: self, didRun: #selector(printOperationDidRun(printOperation:success:contextInfo:)), contextInfo: nil)
}
func cancelJob(_ error: String?) {
printing.onCompleted(printJob: self, completed: false, error: error as NSString?)
func cancelJob(_: String?) {
pdfDocument = nil
semaphore.signal()
}
public static func sharePdf(data: Data, withSourceRect rect: CGRect, andName name: String) {
... ...
... ... @@ -17,12 +17,29 @@
import FlutterMacOS
import Foundation
// Dart:ffi API
private var _printingPlugin: PrintingPlugin?
@_cdecl("net_nfet_printing_set_document")
func setDocument(job: UInt32, doc: UnsafePointer<UInt8>, size: UInt64) {
_printingPlugin!.jobs[job]?.setDocument(Data(bytes: doc, count: Int(size)))
}
@_cdecl("net_nfet_printing_set_error")
func setError(job: UInt32, message: UnsafePointer<CChar>) {
_printingPlugin!.jobs[job]?.cancelJob(String(cString: message))
}
// End of Dart:ffi API
public class PrintingPlugin: NSObject, FlutterPlugin {
private var channel: FlutterMethodChannel
public var jobs = [UInt32: PrintJob]()
init(_ channel: FlutterMethodChannel) {
self.channel = channel
super.init()
_printingPlugin = self
}
/// Entry point
... ... @@ -44,6 +61,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
let marginRight = CGFloat((args["marginRight"] as? NSNumber)?.floatValue ?? 0.0)
let marginBottom = CGFloat((args["marginBottom"] as? NSNumber)?.floatValue ?? 0.0)
let printJob = PrintJob(printing: self, index: args["job"] as! Int)
jobs[args["job"] as! UInt32] = printJob
printJob.printPdf(name: name,
withPageSize: CGSize(
width: width,
... ... @@ -108,13 +126,6 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
andBaseUrl: args["baseUrl"] as? String == nil ? nil : URL(string: args["baseUrl"] as! String)
)
result(NSNumber(value: 1))
// } else if call.method == "pickPrinter" {
// PrintJob.pickPrinter(result: result, 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)
// ))
} else if call.method == "printingInfo" {
result(PrintJob.printingInfo())
} else if call.method == "rasterPdf" {
... ... @@ -143,19 +154,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
"job": printJob.index,
] as [String: Any]
channel.invokeMethod("onLayout", arguments: arg, result: { (result: Any?) -> Void in
if result as? Bool == false {
printJob.cancelJob(nil)
} else if result is FlutterError {
let error = result as! FlutterError
printJob.cancelJob(error.message)
} else if result is FlutterStandardTypedData {
let object = result as! FlutterStandardTypedData
printJob.setDocument(object.data)
} else {
printJob.cancelJob("Unknown data type")
}
})
channel.invokeMethod("onLayout", arguments: arg)
}
/// send completion status to flutter
... ... @@ -166,6 +165,7 @@ public class PrintingPlugin: NSObject, FlutterPlugin {
"job": printJob.index,
]
channel.invokeMethod("onCompleted", arguments: data)
jobs.removeValue(forKey: UInt32(printJob.index))
}
/// send html to pdf data result to flutter
... ...
... ... @@ -11,6 +11,7 @@ environment:
flutter: ">=1.16.0"
dependencies:
ffi: ">=0.2.0-nullsafety <2.0.0"
flutter:
sdk: flutter
flutter_web_plugins:
... ...