-
-
Save LanceMcCarthy/709cbb2acd335f8b118d065ebb55738f to your computer and use it in GitHub Desktop.
Xamarin.Forms PDF Printing Dependency Service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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