using System; using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Printing; using Windows.Data.Pdf; using Windows.Graphics.Printing; using Windows.Graphics.Printing.OptionDetails; using Windows.Storage.Streams; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Printing; namespace Wino.Core.UWP.Services { /// /// Printer service that uses WinRT APIs to print PDF files. /// Used modified version of the code here: /// https://github.com/microsoft/Win2D-Samples/blob/reunion_master/ExampleGallery/PrintingExample.xaml.cs /// HTML file is saved as PDF to temporary location. /// Then PDF is loaded as PdfDocument and printed using CanvasBitmap for each page. /// public class PrintService : IPrintService { private TaskCompletionSource _taskCompletionSource; private CanvasPrintDocument printDocument; private PrintTask printTask; private PdfDocument pdfDocument; private List bitmaps = new(); private Vector2 largestBitmap; private Vector2 pageSize; private Vector2 imagePadding = new Vector2(64, 64); private Vector2 cellSize; private int bitmapCount; private int columns; private int rows; private int bitmapsPerPage; private int pageCount = -1; private PrintInformation _currentPrintInformation; public async Task PrintPdfFileAsync(string pdfFilePath, string printTitle) { if (_taskCompletionSource != null) { _taskCompletionSource.TrySetResult(PrintingResult.Abandoned); _taskCompletionSource = new TaskCompletionSource(); } // Load the PDF file var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfFilePath); pdfDocument = await PdfDocument.LoadFromFileAsync(file); _taskCompletionSource ??= new TaskCompletionSource(); _currentPrintInformation = new PrintInformation(pdfFilePath, printTitle); printDocument = new CanvasPrintDocument(); printDocument.PrintTaskOptionsChanged += OnDocumentTaskOptionsChanged; printDocument.Preview += OnDocumentPreview; printDocument.Print += OnDocumentPrint; var printManager = PrintManager.GetForCurrentView(); printManager.PrintTaskRequested += PrintingExample_PrintTaskRequested; try { await PrintManager.ShowPrintUIAsync(); var result = await _taskCompletionSource.Task; return result; } finally { // Dispose everything. UnregisterPrintManager(printManager); ClearBitmaps(); UnregisterTask(); DisposePDFDocument(); _taskCompletionSource = null; } } private void DisposePDFDocument() { if (pdfDocument != null) { pdfDocument = null; } } private void UnregisterTask() { if (printTask != null) { printTask.Completed -= TaskCompleted; printTask = null; } } private void UnregisterPrintManager(PrintManager manager) { manager.PrintTaskRequested -= PrintingExample_PrintTaskRequested; } private void ClearBitmaps() { foreach (var bitmap in bitmaps) { bitmap.Dispose(); } bitmaps.Clear(); } private void PrintingExample_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args) { if (_currentPrintInformation == null) return; printTask = args.Request.CreatePrintTask(_currentPrintInformation.PDFTitle, (createPrintTaskArgs) => { createPrintTaskArgs.SetSource(printDocument); }); printTask.Completed += TaskCompleted; } private void TaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args) => _taskCompletionSource?.TrySetResult((PrintingResult)args.Completion); private async void OnDocumentTaskOptionsChanged(CanvasPrintDocument sender, CanvasPrintTaskOptionsChangedEventArgs args) { var deferral = args.GetDeferral(); try { await LoadPDFPageBitmapsAsync(sender); var pageDesc = args.PrintTaskOptions.GetPageDescription(1); var newPageSize = pageDesc.PageSize.ToVector2(); if (pageSize == newPageSize && pageCount != -1) { // We've already figured out the pages and the page size hasn't changed, so there's nothing left for us to do here. return; } pageSize = newPageSize; sender.InvalidatePreview(); // Figure out the bitmap index at the top of the current preview page. We'll request that the preview defaults to showing // the page that still has this bitmap on it in the new layout. int indexOnCurrentPage = 0; if (pageCount != -1) { indexOnCurrentPage = (int)(args.CurrentPreviewPageNumber - 1) * bitmapsPerPage; } // Calculate the new layout var printablePageSize = pageSize * 0.9f; cellSize = largestBitmap + imagePadding; var cellsPerPage = printablePageSize / cellSize; columns = Math.Max(1, (int)Math.Floor(cellsPerPage.X)); rows = Math.Max(1, (int)Math.Floor(cellsPerPage.Y)); bitmapsPerPage = columns * rows; // Calculate the page count bitmapCount = bitmaps.Count; pageCount = (int)Math.Ceiling(bitmapCount / (double)bitmapsPerPage); sender.SetPageCount((uint)pageCount); // Set the preview page to the one that has the item that was currently displayed in the last preview args.NewPreviewPageNumber = (uint)(indexOnCurrentPage / bitmapsPerPage) + 1; } finally { deferral.Complete(); } } private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender) { ClearBitmaps(); bitmaps ??= new List(); for (int i = 0; i < pdfDocument.PageCount; i++) { var page = pdfDocument.GetPage((uint)i); var stream = new InMemoryRandomAccessStream(); await page.RenderToStreamAsync(stream); var bitmap = await CanvasBitmap.LoadAsync(sender, stream); bitmaps.Add(bitmap); } largestBitmap = Vector2.Zero; foreach (var bitmap in bitmaps) { largestBitmap.X = Math.Max(largestBitmap.X, (float)bitmap.Size.Width); largestBitmap.Y = Math.Max(largestBitmap.Y, (float)bitmap.Size.Height); } } private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args) { var ds = args.DrawingSession; var pageNumber = args.PageNumber; DrawPdfPage(sender, ds, pageNumber); } private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args) { var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions); int pageCountToPrint = (int)pdfDocument.PageCount; for (uint i = 1; i <= pageCountToPrint; ++i) { using var ds = args.CreateDrawingSession(); var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect; DrawPdfPage(sender, ds, i); } } private void DrawPdfPage(CanvasPrintDocument sender, CanvasDrawingSession ds, uint pageNumber) { if (bitmaps?.Count == 0) return; var cellAcross = new Vector2(cellSize.X, 0); var cellDown = new Vector2(0, cellSize.Y); var totalSize = cellAcross * columns + cellDown * rows; Vector2 topLeft = (pageSize - totalSize) / 2; int bitmapIndex = ((int)pageNumber - 1) * bitmapsPerPage; for (int row = 0; row < rows; ++row) { for (int column = 0; column < columns; ++column) { var cellTopLeft = topLeft + cellAcross * column + cellDown * row; var bitmapInfo = bitmaps[bitmapIndex % bitmaps.Count]; var bitmapPos = cellTopLeft + (cellSize - bitmapInfo.Size.ToVector2()) / 2; ds.DrawImage(bitmapInfo, bitmapPos); bitmapIndex++; } } } } }