Skip to content

Instantly share code, notes, and snippets.

@LanceMcCarthy
Last active January 21, 2022 16:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save LanceMcCarthy/709cbb2acd335f8b118d065ebb55738f to your computer and use it in GitHub Desktop.
Save LanceMcCarthy/709cbb2acd335f8b118d065ebb55738f to your computer and use it in GitHub Desktop.
Xamarin.Forms PDF Printing Dependency Service
// IMPORTANT
// IF you are not familiar with Dependency Service, you MUST learn how to use them before implementing this one.
// Follow the simple tutorial here
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/dependency-service/introduction
// Goes in the Xamarin.Forms project.
namespace YourApp.Portable
{
public interface IPrintService
{
void Print(Stream inputStream, string fileName);
}
}
// Example of using the service
// 1. You have the PDF as a stream in the "pdfFileStream" variable
var pdfFileStream = GetMyDocumentAsStream();
// 2. Pass the tsream reference, and also a desired filename (some platforms save a temp file)
DependencyService.Get<IPrintService>().Print(pdfFileStream, "MyDoc.pdf");
// NATIVE ANDROID IMPLEMENTAITON
using Android.Content;
using Android.OS;
using System;
using System.IO;
using Android.Print;
using Android.PrintServices;
using YourApp.Portable;
using Java.IO;
using Xamarin.Essentials;
[assembly: Xamarin.Forms.Dependency(typeof(PrintService))]
namespace YourApp.Android
{
public class PrintService : IPrintService
{
public void Print(Stream inputStream, string fileName)
{
if (inputStream.CanSeek)
inputStream.Position = 0;
string createdFilePath = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), fileName);
using (var dest = System.IO.File.OpenWrite(createdFilePath))
{
inputStream.CopyTo(dest);
}
string filePath = createdFilePath;
var activity = Xamarin.Essentials.Platform.CurrentActivity;
PrintManager printManager = (PrintManager)activity.GetSystemService(Context.PrintService);
PrintDocumentAdapter pda = new CustomPrintDocumentAdapter(filePath);
printManager.Print(fileName, pda, null);
}
}
internal class CustomPrintDocumentAdapter : PrintDocumentAdapter
{
internal string FileToPrint { get; set; }
internal CustomPrintDocumentAdapter(string fileDesc)
{
FileToPrint = fileDesc;
}
public override void OnLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras)
{
if (cancellationSignal.IsCanceled)
{
callback.OnLayoutCancelled();
return;
}
PrintDocumentInfo pdi = new PrintDocumentInfo.Builder(FileToPrint).SetContentType(PrintContentType.Document).Build();
callback.OnLayoutFinished(pdi, true);
}
public override void OnWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback)
{
InputStream input = null;
OutputStream output = null;
try
{
input = new FileInputStream(FileToPrint);
output = new FileOutputStream(destination.FileDescriptor);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = input.Read(buf)) > 0)
{
output.Write(buf, 0, bytesRead);
}
callback.OnWriteFinished(new[] { PageRange.AllPages });
}
catch (Java.IO.FileNotFoundException ee)
{
}
catch (Exception e)
{
}
finally
{
input?.Close();
output?.Close();
}
}
}
}
// NATIVE iOS IMPLEMENTAITON
using Foundation;
using System;
using System.IO;
using Forms.iOS;
using Forms.Portable;
using UIKit;
[assembly: Xamarin.Forms.Dependency(typeof(PrintService))]
namespace Forms.iOS
{
public class PrintService : IPrintService
{
public void Print(Stream inputStream, string tempFileName)
{
var printInfo = UIPrintInfo.PrintInfo;
printInfo.OutputType = UIPrintInfoOutputType.General;
printInfo.JobName = "Print PDF";
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var library = Path.Combine(documents, "..", "Library");
var filepath = Path.Combine(library, tempFileName);
using (MemoryStream tempStream = new MemoryStream())
{
inputStream.Position = 0;
inputStream.CopyTo(tempStream);
File.WriteAllBytes(filepath, tempStream.ToArray());
}
var printer = UIPrintInteractionController.SharedPrintController;
printInfo.OutputType = UIPrintInfoOutputType.General;
printer.PrintingItem = NSUrl.FromFilename(filepath);
printer.PrintInfo = printInfo;
printer.ShowsPageRange = true;
printer.Present(true, PrintComplete);
}
private void PrintComplete(UIPrintInteractionController controller, bool completed, NSError error)
{
if (!completed)
{
Console.WriteLine("error");
}
}
}
}
// NATIVE UWP IMPLEMENTAITON
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Data.Pdf;
using Windows.Globalization;
using Windows.Graphics.Display;
using Windows.Graphics.Printing;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Printing;
using YourApp.Portable;
using YourApp.UWP;
[assembly: Xamarin.Forms.Dependency(typeof(PrintService))]
namespace YourApp.UWP
{
public class PrintService : IPrintService
{
internal int PageCount;
internal PdfDocument MyPdfDocument;
internal Dictionary<int, UIElement> PrintPreviewPages;
private PrintDocument printDocument;
private IPrintDocumentSource printDocumentSource;
private string fileName;
private double marginWidth;
private double marginHeight;
private readonly Canvas pdfDocumentPanel;
private IRandomAccessStream randomStream;
private PrintTask printTask;
private Image imageCtrl;
private PrintManager printMan;
public PrintService()
{
PrintPreviewPages = new Dictionary<int, UIElement>();
imageCtrl = new Image();
pdfDocumentPanel = new Canvas();
}
public async void Print(Stream inputStream, string tempFileName)
{
this.fileName = tempFileName;
using (var ms = new MemoryStream())
{
inputStream.Position = 0;
await inputStream.CopyToAsync(ms);
ms.Position = 0;
randomStream = await ConvertToRandomAccessStream(ms);
}
MyPdfDocument = await PdfDocument.LoadFromStreamAsync(randomStream);
PageCount = (int)MyPdfDocument.PageCount;
await IncludeCanvas();
DispatcherHelper.RunOnUIThread(async () =>
{
RegisterForPrint();
await PrintManager.ShowPrintUIAsync();
});
}
public async Task<IRandomAccessStream> ConvertToRandomAccessStream(MemoryStream memoryStream)
{
var randomAccessStream = new InMemoryRandomAccessStream();
var contentStream = new MemoryStream();
await memoryStream.CopyToAsync(contentStream);
using (var outputStream = randomAccessStream.GetOutputStreamAt(0))
{
using (var dw = new DataWriter(outputStream))
{
var task = new Task(() => dw.WriteBytes(contentStream.ToArray()));
task.Start();
await task;
await dw.StoreAsync();
await outputStream.FlushAsync();
await dw.FlushAsync();
outputStream.Dispose();
dw.DetachStream();
dw.Dispose();
}
}
return randomAccessStream;
}
private void RegisterForPrint()
{
printDocument = new PrintDocument();
printDocumentSource = printDocument.DocumentSource;
printDocument.Paginate += CreatePrintPreviewPages;
printDocument.GetPreviewPage += GetPrintPreviewPage;
printDocument.AddPages += AddPrintPages;
printMan = PrintManager.GetForCurrentView();
printMan.PrintTaskRequested += PrintTaskRequested;
}
private async void AddPrintPages(object sender, AddPagesEventArgs e)
{
try
{
await PrepareForPrint(0, PageCount);
((PrintDocument)sender).AddPagesComplete();
}
catch
{
((PrintDocument)sender).InvalidatePreview();
}
}
private async Task<int> PrepareForPrint(int p, int count)
{
for (int i = p; i < count; i++)
{
var di = DisplayInformation.GetForCurrentView();
ApplicationLanguages.PrimaryLanguageOverride = CultureInfo.InvariantCulture.TwoLetterISOLanguageName;
using (var pdfPage = MyPdfDocument.GetPage(Convert.ToUInt32(i)))
using(var stream = new InMemoryRandomAccessStream())
{
double pdfPagePreferredZoom = pdfPage.PreferredZoom;
var pdfPageRenderOptions = new PdfPageRenderOptions();
var pdfPageSize = pdfPage.Size;
pdfPageRenderOptions.DestinationHeight = (uint)(pdfPageSize.Height * pdfPagePreferredZoom);
pdfPageRenderOptions.DestinationWidth = (uint)(pdfPageSize.Width * pdfPagePreferredZoom);
await pdfPage.RenderToStreamAsync(stream, pdfPageRenderOptions);
imageCtrl = new Image();
var src = new BitmapImage();
stream.Seek(0);
src.SetSource(stream);
imageCtrl.Source = src;
var dpi = di.LogicalDpi / 96;
imageCtrl.Height = src.PixelHeight / dpi;
imageCtrl.Width = src.PixelWidth / dpi;
printDocument.AddPage(imageCtrl);
}
}
return 0;
}
private void CreatePrintPreviewPages(object sender, PaginateEventArgs e)
{
var pageDescription = e.PrintTaskOptions.GetPageDescription((uint)e.CurrentPreviewPageNumber);
marginWidth = pageDescription.PageSize.Width;
marginHeight = pageDescription.PageSize.Height;
AddOnePrintPreviewPage();
((PrintDocument)sender).SetPreviewPageCount(PageCount, PreviewPageCountType.Final);
}
private void AddOnePrintPreviewPage()
{
for (int i = pdfDocumentPanel.Children.Count - 1; i >= 0; i--)
{
if (pdfDocumentPanel.Children[i] is Canvas print)
{
print.Width = marginWidth;
print.Height = marginHeight;
PrintPreviewPages.Add(i, print);
}
}
}
private void GetPrintPreviewPage(object sender, GetPreviewPageEventArgs e)
{
var printDoc = (PrintDocument)sender;
pdfDocumentPanel.Children.Remove(PrintPreviewPages[e.PageNumber - 1]);
printDoc.SetPreviewPage(e.PageNumber, PrintPreviewPages[e.PageNumber - 1]);
}
private async Task<int> IncludeCanvas()
{
for (int i = 0; i < PageCount; i++)
{
using (var pdfPage = MyPdfDocument.GetPage(Convert.ToUInt32(i)))
using (IRandomAccessStream stream = new InMemoryRandomAccessStream())
{
double width = pdfPage.Size.Width;
double height = pdfPage.Size.Height;
var page = new Canvas
{
Width = width,
Height = height,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Center,
Background = new SolidColorBrush(global::Windows.UI.Color.FromArgb(255, 255, 255, 255)),
Margin = new Thickness(0, 0, 0, 0)
};
double pdfPagePreferredZoom = pdfPage.PreferredZoom;
var pdfPageRenderOptions = new PdfPageRenderOptions();
var pdfPageSize = pdfPage.Size;
pdfPageRenderOptions.DestinationHeight = (uint)(pdfPageSize.Height * pdfPagePreferredZoom);
pdfPageRenderOptions.DestinationWidth = (uint)(pdfPageSize.Width * pdfPagePreferredZoom);
await pdfPage.RenderToStreamAsync(stream, pdfPageRenderOptions);
imageCtrl = new Image();
BitmapImage src = new BitmapImage();
stream.Seek(0);
src.SetSource(stream);
imageCtrl.Source = src;
var di = DisplayInformation.GetForCurrentView();
var dpi = di.LogicalDpi / 96;
imageCtrl.Height = src.PixelHeight / dpi;
imageCtrl.Width = src.PixelWidth / dpi;
page.Children.Add(imageCtrl);
pdfDocumentPanel.Children.Add(page);
}
}
return 0;
}
private void PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs e)
{
var deferral = e.Request.GetDeferral();
printTask = e.Request.CreatePrintTask(fileName, sourceRequested => sourceRequested.SetSource(printDocumentSource));
printTask.Completed += printTask_Completed;
deferral.Complete();
}
private void printTask_Completed(PrintTask sender, PrintTaskCompletedEventArgs args)
{
printTask.Completed -= printTask_Completed;
printMan.PrintTaskRequested -= PrintTaskRequested;
DispatcherHelper.RunOnUIThread(async () =>
{
await new MessageDialog("Printing operation has been completed").ShowAsync();
});
}
}
internal class DispatcherHelper
{
private static readonly CoreDispatcher Dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
internal static void RunOnUIThread(Action action)
{
if (CoreApplication.MainView.CoreWindow == null || Dispatcher.HasThreadAccess)
action();
else
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask().Wait();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment