David PHAM-VAN

Add html to pdf platform conversion

... ... @@ -28,6 +28,7 @@ printing/android/local.properties
printing/*.log
printing/ios/Flutter
printing/ios/Runner
printing/android/.gradle
.pana
pedantic_analyis_options_*.yaml
... ...
## 2.1.0
* Add html to pdf platform conversion
## 2.0.4
* Update Readme
... ...
/*
* 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.
*/
package android.print;
import android.app.Activity;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class PdfConvert {
public static void print(final Activity activity, final PrintDocumentAdapter adapter,
final PrintAttributes attributes, final Result result) {
adapter.onLayout(null, attributes, null, new PrintDocumentAdapter.LayoutResultCallback() {
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
File outputDir = activity.getCacheDir();
File outputFile = null;
try {
outputFile = File.createTempFile("printing", "pdf", outputDir);
} catch (IOException e) {
outputFile.delete();
result.onError(e.getMessage());
return;
}
try {
final File finalOutputFile = outputFile;
adapter.onWrite(new PageRange[] {PageRange.ALL_PAGES},
ParcelFileDescriptor.open(
outputFile, ParcelFileDescriptor.MODE_READ_WRITE),
new CancellationSignal(),
new PrintDocumentAdapter.WriteResultCallback() {
@Override
public void onWriteFinished(PageRange[] pages) {
super.onWriteFinished(pages);
if (pages.length == 0) {
finalOutputFile.delete();
result.onError("No page created");
}
result.onSuccess(finalOutputFile);
finalOutputFile.delete();
}
});
} catch (FileNotFoundException e) {
outputFile.delete();
result.onError(e.getMessage());
}
}
}, null);
}
public static byte[] readFile(File file) throws IOException {
byte[] buffer = new byte[(int) file.length()];
InputStream ios = null;
try {
ios = new FileInputStream(file);
if (ios.read(buffer) == -1) {
throw new IOException("EOF reached while trying to read the whole file");
}
} finally {
try {
if (ios != null) ios.close();
} catch (IOException e) {
}
}
return buffer;
}
public interface Result {
void onSuccess(File file);
void onError(String message);
}
}
... ...
... ... @@ -16,22 +16,24 @@
package net.nfet.flutter.printing;
import static java.lang.StrictMath.max;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PdfConvert;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintManager;
import android.print.pdf.PrintedPdfDocument;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.core.content.FileProvider;
import java.io.File;
... ... @@ -155,6 +157,28 @@ public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHa
sharePdf((byte[]) call.argument("doc"), (String) call.argument("name"));
result.success(0);
break;
case "convertHtml":
double width = (double) call.argument("width");
double height = (double) call.argument("height");
double marginLeft = (double) call.argument("marginLeft");
double marginTop = (double) call.argument("marginTop");
double marginRight = (double) call.argument("marginRight");
double marginBottom = (double) call.argument("marginBottom");
PrintAttributes.Margins margins =
new PrintAttributes.Margins(Double.valueOf(marginLeft * 1000.0).intValue(),
Double.valueOf(marginTop * 1000.0 / 72.0).intValue(),
Double.valueOf(marginRight * 1000.0 / 72.0).intValue(),
Double.valueOf(marginBottom * 1000.0 / 72.0).intValue());
PrintAttributes.MediaSize size = new PrintAttributes.MediaSize("flutter_printing",
"Provided size", Double.valueOf(width * 1000.0 / 72.0).intValue(),
Double.valueOf(height * 1000.0 / 72.0).intValue());
convertHtml((String) call.argument("html"), size, margins,
(String) call.argument("baseUrl"));
result.success(0);
break;
default:
result.notImplemented();
break;
... ... @@ -192,4 +216,49 @@ public class PrintingPlugin extends PrintDocumentAdapter implements MethodCallHa
e.printStackTrace();
}
}
private void convertHtml(String data, final PrintAttributes.MediaSize size,
final PrintAttributes.Margins margins, String baseUrl) {
final WebView webView = new WebView(activity.getApplicationContext());
webView.loadDataWithBaseURL(baseUrl, data, "text/HTML", "UTF-8", null);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
PrintAttributes attributes =
new PrintAttributes.Builder()
.setMediaSize(size)
.setResolution(
new PrintAttributes.Resolution("pdf", "pdf", 600, 600))
.setMinMargins(margins)
.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final PrintDocumentAdapter adapter =
webView.createPrintDocumentAdapter("printing");
PdfConvert.print(activity, adapter, attributes, new PdfConvert.Result() {
@Override
public void onSuccess(File file) {
try {
byte[] fileContent = PdfConvert.readFile(file);
channel.invokeMethod("onHtmlRendered", fileContent);
} catch (IOException e) {
onError(e.getMessage());
}
}
@Override
public void onError(String message) {
channel.invokeMethod("onHtmlError", message);
}
});
}
}
}
});
}
}
... ...
<!DOCTYPE html>
<html>
<head>
<style>
* {
font-family: Arial, Helvetica, sans-serif;
}
table,
th,
td {
border: 1px solid black;
border-collapse: collapse;
}
th,
td {
padding: 5px;
}
</style>
</head>
<body>
<h2>Html conversion</h2>
<caption>Sample HTML Table</caption>
<table>
<tr>
<th>Company</th>
<th>Contact</th>
<th>Country</th>
</tr>
<tr>
<td>Alfreds Futterkiste</td>
<td>Maria Anders</td>
<td>Germany</td>
</tr>
<tr>
<td>Centro comercial Moctezuma</td>
<td>Francisco Chang</td>
<td>Mexico</td>
</tr>
<tr>
<td>Ernst Handel</td>
<td>Roland Mendel</td>
<td>Austria</td>
</tr>
<tr>
<td>Island Trading</td>
<td>Helen Bennett</td>
<td>UK</td>
</tr>
<tr>
<td>Laughing Bacchus Winecellars</td>
<td>Yoshi Tannamuri</td>
<td>Canada</td>
</tr>
<tr>
<td>Magazzini Alimentari Riuniti</td>
<td>Giovanni Rovelli</td>
<td>Italy</td>
</tr>
</table>
<p>HTML links are defined with the a tag:</p>
<a href="https://github.com/DavBfr/dart_pdf">This is a link</a>
<h2>An Unordered HTML List</h2>
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
<h2>An Ordered HTML List</h2>
<ol>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ol>
</body>
</html>
... ...
... ... @@ -5,6 +5,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
... ... @@ -94,6 +95,14 @@ class MyAppState extends State<MyApp> {
});
}
Future<void> _printHtml() async {
print('Print html ...');
await Printing.layoutPdf(onLayout: (PdfPageFormat format) async {
final String html = await rootBundle.loadString('assets/example.html');
return await Printing.convertHtml(format: format, html: html);
});
}
@override
Widget build(BuildContext context) {
return RepaintBoundary(
... ... @@ -117,6 +126,8 @@ class MyAppState extends State<MyApp> {
onPressed: _printScreen),
RaisedButton(
child: const Text('Save to file'), onPressed: _saveAsFile),
RaisedButton(
child: const Text('Print Html'), onPressed: _printHtml),
],
),
),
... ...
... ... @@ -26,3 +26,5 @@ dependency_overrides:
flutter:
uses-material-design: true
assets:
- assets/
... ...
... ... @@ -16,10 +16,12 @@
import Flutter
import UIKit
import WebKit
public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionControllerDelegate {
private var channel: FlutterMethodChannel?
private var renderer: PdfPrintPageRenderer?
private var urlObservation: NSKeyValueObservation?
init(_ channel: FlutterMethodChannel?) {
super.init()
... ... @@ -52,6 +54,30 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon
)
}
result(NSNumber(value: 1))
} else if call.method == "convertHtml" {
let width = CGFloat((args["width"] as? NSNumber)?.floatValue ?? 0.0)
let height = CGFloat((args["height"] as? NSNumber)?.floatValue ?? 0.0)
let marginLeft = CGFloat((args["marginLeft"] as? NSNumber)?.floatValue ?? 0.0)
let marginTop = CGFloat((args["marginTop"] as? NSNumber)?.floatValue ?? 0.0)
let marginRight = CGFloat((args["marginRight"] as? NSNumber)?.floatValue ?? 0.0)
let marginBottom = CGFloat((args["marginBottom"] as? NSNumber)?.floatValue ?? 0.0)
convertHtml(
(args["html"] as? String)!,
withPageSize: CGRect(
x: 0.0,
y: 0.0,
width: width,
height: height
),
andMargin: CGRect(
x: marginLeft,
y: marginTop,
width: width - marginRight - marginLeft,
height: height - marginBottom - marginTop
),
andBaseUrl: args["baseUrl"] as? String == nil ? nil : URL(string: (args["baseUrl"] as? String)!)
)
result(NSNumber(value: 1))
} else {
result(FlutterMethodNotImplemented)
}
... ... @@ -118,4 +144,56 @@ public class SwiftPrintingPlugin: NSObject, FlutterPlugin, UIPrintInteractionCon
}
UIApplication.shared.keyWindow?.rootViewController?.present(activityViewController, animated: true)
}
func convertHtml(_ data: String, withPageSize rect: CGRect, andMargin margin: CGRect, andBaseUrl baseUrl: URL?) {
let viewController = UIApplication.shared.delegate?.window?!.rootViewController
let wkWebView = WKWebView(frame: viewController!.view.bounds)
wkWebView.isHidden = true
wkWebView.tag = 100
viewController?.view.addSubview(wkWebView)
wkWebView.loadHTMLString(data, baseURL: baseUrl ?? Bundle.main.bundleURL)
urlObservation = wkWebView.observe(\.isLoading, changeHandler: { _, _ in
// this is workaround for issue with loading local images
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// assign the print formatter to the print page renderer
let renderer = UIPrintPageRenderer()
renderer.addPrintFormatter(wkWebView.viewPrintFormatter(), startingAtPageAt: 0)
// assign paperRect and printableRect values
renderer.setValue(rect, forKey: "paperRect")
renderer.setValue(margin, forKey: "printableRect")
// create pdf context and draw each page
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, .zero, nil)
for i in 0 ..< renderer.numberOfPages {
UIGraphicsBeginPDFPage()
renderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
UIGraphicsEndPDFContext()
if let viewWithTag = viewController?.view.viewWithTag(wkWebView.tag) {
viewWithTag.removeFromSuperview() // remove hidden webview when pdf is generated
// clear WKWebView cache
if #available(iOS 9.0, *) {
WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
records.forEach { record in
WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {})
}
}
}
}
// dispose urlObservation
self.urlObservation = nil
let data = FlutterStandardTypedData(bytes: pdfData as Data)
self.channel?.invokeMethod("onHtmlRendered", arguments: data)
}
})
}
}
... ...
... ... @@ -21,6 +21,7 @@ typedef LayoutCallback = FutureOr<List<int>> Function(PdfPageFormat format);
mixin Printing {
static const MethodChannel _channel = MethodChannel('printing');
static LayoutCallback _onLayout;
static Completer<List<int>> _onHtmlRendered;
static Future<void> _handleMethod(MethodCall call) async {
switch (call.method) {
... ... @@ -37,6 +38,12 @@ mixin Printing {
'doc': Uint8List.fromList(bytes),
};
return await _channel.invokeMethod('writePdf', params);
case 'onHtmlRendered':
_onHtmlRendered.complete(call.arguments);
break;
case 'onHtmlError':
_onHtmlRendered.completeError(call.arguments);
break;
}
}
... ... @@ -89,4 +96,25 @@ mixin Printing {
};
return await _channel.invokeMethod('sharePdf', params);
}
static Future<List<int>> convertHtml(
{@required String html,
String baseUrl,
PdfPageFormat format = PdfPageFormat.a4}) async {
final Map<String, dynamic> params = <String, dynamic>{
'html': html,
'baseUrl': baseUrl,
'width': format.width,
'height': format.height,
'marginLeft': format.marginLeft,
'marginTop': format.marginTop,
'marginRight': format.marginRight,
'marginBottom': format.marginBottom,
};
_channel.setMethodCallHandler(_handleMethod);
_onHtmlRendered = Completer<List<int>>();
await _channel.invokeMethod<void>('convertHtml', params);
return _onHtmlRendered.future;
}
}
... ...
... ... @@ -4,7 +4,7 @@ description: Plugin that allows Flutter apps to generate and print documents to
homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing
repository: https://github.com/DavBfr/dart_pdf
issue_tracker: https://github.com/DavBfr/dart_pdf/issues
version: 2.0.4
version: 2.1.0
environment:
sdk: ">=2.1.0 <3.0.0"
... ...