David PHAM-VAN

Add Flutter Printing plugin

@@ -12,3 +12,14 @@ doc/api/ @@ -12,3 +12,14 @@ doc/api/
12 12
13 *.pdf 13 *.pdf
14 *.ttf 14 *.ttf
  15 +.DS_Store
  16 +.dart_tool/
  17 +
  18 +.packages
  19 +.pub/
  20 +pubspec.lock
  21 +
  22 +build/
  23 +printing/android/local.properties
  24 +.idea
  25 +*.iml
  1 +# 1.0.0
  2 +* Initial release.
  1 +TODO: Add your license here.
  1 +# Printing
  2 +
  3 +Plugin that allows Flutter apps to generate and print documents to android or ios compatible printers
  4 +
  5 +## Getting Started
  6 +
  7 +For help getting started with Flutter, view our online
  8 +[documentation](https://flutter.io/).
  9 +
  10 +For help on editing plugin code, view the [documentation](https://flutter.io/developing-packages/#edit-plugin-package).
  1 +group 'net.nfet.flutter.printing'
  2 +version '1.0-SNAPSHOT'
  3 +
  4 +buildscript {
  5 + repositories {
  6 + google()
  7 + jcenter()
  8 + }
  9 +
  10 + dependencies {
  11 + classpath 'com.android.tools.build:gradle:3.1.2'
  12 + }
  13 +}
  14 +
  15 +rootProject.allprojects {
  16 + repositories {
  17 + google()
  18 + jcenter()
  19 + }
  20 +}
  21 +
  22 +apply plugin: 'com.android.library'
  23 +
  24 +android {
  25 + compileSdkVersion 27
  26 +
  27 + defaultConfig {
  28 + minSdkVersion 19
  29 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  30 + }
  31 + lintOptions {
  32 + disable 'InvalidPackage'
  33 + }
  34 +}
  35 +
  36 +dependencies {
  37 + api 'com.android.support:appcompat-v7:27.1.1'
  38 +}
  1 +org.gradle.jvmargs=-Xmx1536M
  1 +rootProject.name = 'printing'
  1 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2 + package="net.nfet.flutter.printing">
  3 +
  4 + <application>
  5 + <provider
  6 + android:name=".PrintFileProvider"
  7 + android:authorities="${applicationId}.flutter.printing"
  8 + android:exported="false"
  9 + android:grantUriPermissions="true">
  10 + <meta-data
  11 + android:name="android.support.FILE_PROVIDER_PATHS"
  12 + android:resource="@xml/flutter_printing_file_paths"/>
  13 + </provider>
  14 + </application>
  15 +
  16 +</manifest>
  1 +/*
  2 + * Copyright (C) 2018, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * This library is free software; you can redistribute it and/or
  5 + * modify it under the terms of the GNU Lesser General
  6 + * License as published by the Free Software Foundation; either
  7 + * version 2.1 of the License, or (at your option) any later version.
  8 + *
  9 + * This library is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12 + * Lesser General License for more details.
  13 + *
  14 + * You should have received a copy of the GNU Lesser General
  15 + * License along with this library; if not, write to the Free Software
  16 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17 + */
  18 +
  19 +package net.nfet.flutter.printing;
  20 +
  21 +import android.support.v4.content.FileProvider;
  22 +
  23 +public class PrintFileProvider extends FileProvider {
  24 +}
  1 +/*
  2 + * Copyright (C) 2018, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * This library is free software; you can redistribute it and/or
  5 + * modify it under the terms of the GNU Lesser General
  6 + * License as published by the Free Software Foundation; either
  7 + * version 2.1 of the License, or (at your option) any later version.
  8 + *
  9 + * This library is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12 + * Lesser General License for more details.
  13 + *
  14 + * You should have received a copy of the GNU Lesser General
  15 + * License along with this library; if not, write to the Free Software
  16 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17 + */
  18 +
  19 +package net.nfet.flutter.printing;
  20 +
  21 +import android.app.Activity;
  22 +import android.content.Context;
  23 +import android.content.Intent;
  24 +import android.net.Uri;
  25 +import android.os.Bundle;
  26 +import android.os.CancellationSignal;
  27 +import android.os.Environment;
  28 +import android.os.ParcelFileDescriptor;
  29 +import android.print.PageRange;
  30 +import android.print.PrintAttributes;
  31 +import android.print.PrintDocumentAdapter;
  32 +import android.print.PrintDocumentInfo;
  33 +import android.print.PrintManager;
  34 +import android.print.pdf.PrintedPdfDocument;
  35 +import android.support.v4.content.FileProvider;
  36 +
  37 +import java.io.File;
  38 +import java.io.FileOutputStream;
  39 +import java.io.IOException;
  40 +import java.io.OutputStream;
  41 +
  42 +import io.flutter.plugin.common.MethodCall;
  43 +import io.flutter.plugin.common.MethodChannel;
  44 +import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
  45 +import io.flutter.plugin.common.MethodChannel.Result;
  46 +import io.flutter.plugin.common.PluginRegistry.Registrar;
  47 +
  48 +/**
  49 + * PrintingPlugin
  50 + */
  51 +public class PrintingPlugin implements MethodCallHandler {
  52 + private static PrintManager printManager;
  53 + private final Activity activity;
  54 +
  55 + private PrintingPlugin(Activity activity) {
  56 + this.activity = activity;
  57 + }
  58 +
  59 + /**
  60 + * Plugin registration.
  61 + */
  62 + public static void registerWith(Registrar registrar) {
  63 + final MethodChannel channel = new MethodChannel(registrar.messenger(), "printing");
  64 + channel.setMethodCallHandler(new PrintingPlugin(registrar.activity()));
  65 + printManager = (PrintManager) registrar.activity().getSystemService(Context.PRINT_SERVICE);
  66 + }
  67 +
  68 + @Override
  69 + public void onMethodCall(MethodCall call, Result result) {
  70 + switch (call.method) {
  71 + case "printPdf":
  72 + printPdf((byte[]) call.argument("doc"));
  73 + result.success(0);
  74 + break;
  75 + case "sharePdf":
  76 + sharePdf((byte[]) call.argument("doc"));
  77 + result.success(0);
  78 + break;
  79 + default:
  80 + result.notImplemented();
  81 + break;
  82 + }
  83 + }
  84 +
  85 +
  86 + private void printPdf(final byte[] badgeData) {
  87 + PrintDocumentAdapter pda = new PrintDocumentAdapter() {
  88 + PrintedPdfDocument mPdfDocument;
  89 +
  90 + @Override
  91 + public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor parcelFileDescriptor, CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) {
  92 +
  93 + OutputStream output = null;
  94 + try {
  95 + output = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
  96 + output.write(badgeData, 0, badgeData.length);
  97 + writeResultCallback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
  98 + } catch (IOException e) {
  99 + e.printStackTrace();
  100 + } finally {
  101 + try {
  102 + if (output != null) {
  103 + output.close();
  104 + }
  105 + } catch (IOException e) {
  106 + e.printStackTrace();
  107 + }
  108 + }
  109 + }
  110 +
  111 + @Override
  112 + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) {
  113 + // Create a new PdfDocument with the requested page attributes
  114 + mPdfDocument = new PrintedPdfDocument(activity, newAttributes);
  115 +
  116 + // Respond to cancellation request
  117 + if (cancellationSignal.isCanceled()) {
  118 + callback.onLayoutCancelled();
  119 + return;
  120 + }
  121 +
  122 + // Return print information to print framework
  123 + PrintDocumentInfo info = new PrintDocumentInfo
  124 + .Builder("badge.pdf")
  125 + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
  126 + .setPageCount(1).build();
  127 + // Content layout reflow is complete
  128 + callback.onLayoutFinished(info, true);
  129 + }
  130 +
  131 + @Override
  132 + public void onFinish() {
  133 + //noinspection ResultOfMethodCallIgnored
  134 + }
  135 + };
  136 + String jobName = "Badge";
  137 + printManager.print(jobName, pda, null);
  138 + }
  139 +
  140 + private void sharePdf(byte[] data) {
  141 + try {
  142 +
  143 + final File externalFilesDirectory = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
  144 + File shareFile = File.createTempFile("badge", ".pdf", externalFilesDirectory);
  145 +
  146 + FileOutputStream stream = new FileOutputStream(shareFile);
  147 + stream.write(data);
  148 + stream.close();
  149 +
  150 + Uri apkURI = FileProvider.getUriForFile(activity,
  151 + activity.getApplicationContext().getPackageName() + ".flutter.printing", shareFile);
  152 +
  153 + Intent shareIntent = new Intent();
  154 + shareIntent.setAction(Intent.ACTION_SEND);
  155 + shareIntent.setType("application/pdf");
  156 + shareIntent.putExtra(Intent.EXTRA_STREAM, apkURI);
  157 + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  158 + Intent chooserIntent = Intent.createChooser(shareIntent, null);
  159 + activity.startActivity(chooserIntent);
  160 + shareFile.deleteOnExit();
  161 + } catch (IOException e) {
  162 + e.printStackTrace();
  163 + }
  164 + }
  165 +
  166 +}
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<paths>
  3 + <external-path name="external_files" path="."/>
  4 +</paths>
  1 +import 'dart:ui';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +import 'package:pdf/pdf.dart';
  5 +import 'package:printing/printing.dart';
  6 +
  7 +void main() => runApp(new MaterialApp(home: new MyApp()));
  8 +
  9 +class MyApp extends StatelessWidget {
  10 + final shareWidget = new GlobalKey();
  11 +
  12 + PDFDocument _generateDocument() {
  13 + final pdf = new PDFDocument();
  14 + final page = new PDFPage(pdf, pageFormat: PDFPageFormat.A4);
  15 + final g = page.getGraphics();
  16 + final font = new PDFFont(pdf);
  17 + final top = page.pageFormat.height;
  18 +
  19 + g.setColor(new PDFColor(0.0, 1.0, 1.0));
  20 + g.drawRect(50.0 * PDFPageFormat.MM, top - 80.0 * PDFPageFormat.MM,
  21 + 100.0 * PDFPageFormat.MM, 50.0 * PDFPageFormat.MM);
  22 + g.fillPath();
  23 +
  24 + g.setColor(new PDFColor(0.3, 0.3, 0.3));
  25 + g.drawString(
  26 + font, 12.0, "Hello World!", 10.0 * PDFPageFormat.MM, top - 10.0 * PDFPageFormat.MM);
  27 +
  28 + return pdf;
  29 + }
  30 +
  31 + void _printPdf() {
  32 + print("Print ...");
  33 + final pdf = _generateDocument();
  34 + Printing.printPdf(document: pdf);
  35 + }
  36 +
  37 + void _sharePdf() {
  38 + print("Share ...");
  39 + final pdf = _generateDocument();
  40 +
  41 + // Calculate the widget center for iPad sharing popup position
  42 + final RenderBox referenceBox = shareWidget.currentContext.findRenderObject();
  43 + final topLeft = referenceBox.localToGlobal(referenceBox.paintBounds.topLeft);
  44 + final bottomRight = referenceBox.localToGlobal(referenceBox.paintBounds.bottomRight);
  45 + final bounds = new Rect.fromPoints(topLeft, bottomRight);
  46 +
  47 + Printing.sharePdf(document: pdf, bounds: bounds);
  48 + }
  49 +
  50 + @override
  51 + Widget build(BuildContext context) {
  52 + return new Scaffold(
  53 + appBar: new AppBar(
  54 + title: const Text('Printing example'),
  55 + ),
  56 + body: new Center(
  57 + child: new Column(
  58 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  59 + children: <Widget>[
  60 + new RaisedButton(child: new Text('Print Document'), onPressed: _printPdf),
  61 + new RaisedButton(
  62 + key: shareWidget, child: new Text('Share Document'), onPressed: _sharePdf),
  63 + ],
  64 + ),
  65 + ),
  66 + );
  67 + }
  68 +}
  1 +/*
  2 + * Copyright (C) 2018, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * This library is free software; you can redistribute it and/or
  5 + * modify it under the terms of the GNU Lesser General
  6 + * License as published by the Free Software Foundation; either
  7 + * version 2.1 of the License, or (at your option) any later version.
  8 + *
  9 + * This library is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12 + * Lesser General License for more details.
  13 + *
  14 + * You should have received a copy of the GNU Lesser General
  15 + * License along with this library; if not, write to the Free Software
  16 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17 + */
  18 +
  19 +#import <Flutter/Flutter.h>
  20 +
  21 +@interface PrintingPlugin : NSObject<FlutterPlugin, UIPrintInteractionControllerDelegate>
  22 +@end
  1 +/*
  2 + * Copyright (C) 2018, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * This library is free software; you can redistribute it and/or
  5 + * modify it under the terms of the GNU Lesser General
  6 + * License as published by the Free Software Foundation; either
  7 + * version 2.1 of the License, or (at your option) any later version.
  8 + *
  9 + * This library is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12 + * Lesser General License for more details.
  13 + *
  14 + * You should have received a copy of the GNU Lesser General
  15 + * License along with this library; if not, write to the Free Software
  16 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17 + */
  18 +
  19 +#import "PrintingPlugin.h"
  20 +
  21 +@implementation PrintingPlugin
  22 ++ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  23 + FlutterMethodChannel* channel = [FlutterMethodChannel
  24 + methodChannelWithName:@"printing"
  25 + binaryMessenger:[registrar messenger]];
  26 + PrintingPlugin* instance = [[PrintingPlugin alloc] init];
  27 + [registrar addMethodCallDelegate:instance channel:channel];
  28 +}
  29 +
  30 +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  31 + if ([@"printPdf" isEqualToString:call.method]) {
  32 + [self printPdf:[call.arguments objectForKey:@"doc"]];
  33 + result(@1);
  34 + } else if ([@"sharePdf" isEqualToString:call.method]) {
  35 + [self
  36 + sharePdf:[call.arguments objectForKey:@"doc"]
  37 + withSourceRect:CGRectMake(
  38 + [[call.arguments objectForKey:@"x"] floatValue],
  39 + [[call.arguments objectForKey:@"y"] floatValue],
  40 + [[call.arguments objectForKey:@"w"] floatValue],
  41 + [[call.arguments objectForKey:@"h"] floatValue])];
  42 + result(@1);
  43 + } else {
  44 + result(FlutterMethodNotImplemented);
  45 + }
  46 +}
  47 +
  48 +- (void)printPdf:(nonnull FlutterStandardTypedData *)data {
  49 + BOOL printing = [UIPrintInteractionController isPrintingAvailable];
  50 + if (!printing) {
  51 + NSLog(@"printing not available");
  52 + return;
  53 + }
  54 +
  55 + BOOL dataOK = [UIPrintInteractionController canPrintData:[data data]];
  56 + if (!dataOK) NSLog(@"data not ok to be printed");
  57 +
  58 + UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
  59 + [controller setDelegate:self];
  60 + [controller setPrintingItem:[data data]];
  61 +
  62 + UIPrintInteractionCompletionHandler completionHandler = ^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
  63 + if(!completed && error){
  64 + NSLog(@"FAILED! due to error in domain %@ with error code %u", error.domain, (unsigned int)error.code);
  65 + }
  66 + };
  67 +
  68 + [controller presentAnimated:YES completionHandler:completionHandler];
  69 +}
  70 +
  71 +- (void)sharePdf:(nonnull FlutterStandardTypedData *)data withSourceRect:(CGRect)rect {
  72 + NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
  73 +
  74 + CFUUIDRef uuid = CFUUIDCreate(NULL);
  75 + assert(uuid != NULL);
  76 +
  77 + CFStringRef uuidStr = CFUUIDCreateString(NULL, uuid);
  78 + assert(uuidStr != NULL);
  79 +
  80 + NSURL *fileURL = [[tmpDirURL URLByAppendingPathComponent:[NSString stringWithFormat:@"pdf-%@", uuidStr]] URLByAppendingPathExtension:@"pdf"];
  81 + assert(fileURL != NULL);
  82 +
  83 + CFRelease(uuidStr);
  84 + CFRelease(uuid);
  85 +
  86 + NSString *path = [fileURL path];
  87 +
  88 + NSError *error;
  89 + if (![[data data] writeToFile:path options:NSDataWritingAtomic error:&error]) {
  90 + NSLog(@"sharePdf error: %@", [error localizedDescription]);
  91 + return;
  92 + }
  93 +
  94 + UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil];
  95 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  96 + UIViewController *controller = [UIApplication sharedApplication].keyWindow.rootViewController;
  97 + activityViewController.popoverPresentationController.sourceView = controller.view;
  98 + activityViewController.popoverPresentationController.sourceRect = rect;
  99 + }
  100 + [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:activityViewController animated:YES completion:nil];
  101 +}
  102 +
  103 +@end
  1 +#
  2 +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
  3 +#
  4 +Pod::Spec.new do |s|
  5 + s.name = 'printing'
  6 + s.version = '0.0.1'
  7 + s.summary = 'Plugin that allows Flutter apps to generate and print documents to android or ios compatible printers'
  8 + s.description = <<-DESC
  9 +Plugin that allows Flutter apps to generate and print documents to android or ios compatible printers
  10 + DESC
  11 + s.homepage = 'http://example.com'
  12 + s.license = { :file => '../LICENSE' }
  13 + s.author = { 'Your Company' => 'email@example.com' }
  14 + s.source = { :path => '.' }
  15 + s.source_files = 'Classes/**/*'
  16 + s.public_header_files = 'Classes/**/*.h'
  17 + s.dependency 'Flutter'
  18 +
  19 + s.ios.deployment_target = '8.0'
  20 +end
  21 +
  1 +/*
  2 + * Copyright (C) 2018, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * This library is free software; you can redistribute it and/or
  5 + * modify it under the terms of the GNU Lesser General
  6 + * License as published by the Free Software Foundation; either
  7 + * version 2.1 of the License, or (at your option) any later version.
  8 + *
  9 + * This library is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12 + * Lesser General License for more details.
  13 + *
  14 + * You should have received a copy of the GNU Lesser General
  15 + * License along with this library; if not, write to the Free Software
  16 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17 + */
  18 +
  19 +import 'dart:async';
  20 +import 'dart:ui';
  21 +
  22 +import 'package:flutter/services.dart';
  23 +import 'package:flutter/widgets.dart';
  24 +import 'package:pdf/pdf.dart';
  25 +
  26 +class Printing {
  27 + static const MethodChannel _channel = const MethodChannel('printing');
  28 +
  29 + static Future<Null> printPdf({PDFDocument document, List<int> bytes}) async {
  30 + assert(document != null || bytes != null);
  31 + assert(!(document == null && bytes == null));
  32 +
  33 + if (document != null) bytes = document.save();
  34 +
  35 + final Map<String, dynamic> params = <String, dynamic>{
  36 + 'doc': bytes,
  37 + };
  38 + await _channel.invokeMethod('printPdf', params);
  39 + }
  40 +
  41 + static Future<Null> sharePdf({PDFDocument document, List<int> bytes, Rect bounds}) async {
  42 + assert(document != null || bytes != null);
  43 + assert(!(document == null && bytes == null));
  44 +
  45 + if (document != null) bytes = document.save();
  46 +
  47 + if (bounds == null) {
  48 + bounds = new Rect.fromCircle(center: Offset.zero, radius: 10.0);
  49 + }
  50 +
  51 + final Map<String, dynamic> params = <String, dynamic>{
  52 + 'doc': bytes,
  53 + 'x': bounds.left,
  54 + 'y': bounds.top,
  55 + 'w': bounds.width,
  56 + 'h': bounds.height,
  57 + };
  58 + await _channel.invokeMethod('sharePdf', params);
  59 + }
  60 +}
  1 +name: printing
  2 +author: David PHAM-VAN <dev.nfet.net@gmail.com>
  3 +description: Plugin that allows Flutter apps to generate and print documents to android or ios compatible printers
  4 +homepage: https://github.com/davbfr/dart_pdf/printing
  5 +version: 1.0.0
  6 +
  7 +environment:
  8 + sdk: ">=1.19.0 <2.0.0"
  9 +
  10 +dependencies:
  11 + flutter:
  12 + sdk: flutter
  13 + pdf: "^1.0.3"
  14 +
  15 +flutter:
  16 + plugin:
  17 + androidPackage: net.nfet.flutter.printing
  18 + pluginClass: PrintingPlugin