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.Display;
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)
{
System.Diagnostics.Debug.WriteLine("[PrintService] OnDocumentTaskOptionsChanged starting...");
DisplayInformation di = DisplayInformation.GetForCurrentView();
System.Diagnostics.Debug.WriteLine($"[PrintService] DisplayInformation.RawPixelsPerViewPixel: {di.RawPixelsPerViewPixel}");
System.Diagnostics.Debug.WriteLine($"[PrintService] DisplayInformation.LogicalDpi: {di.LogicalDpi}");
var deferral = args.GetDeferral();
try
{
await LoadPDFPageBitmapsAsync(sender);
var pageDesc = args.PrintTaskOptions.GetPageDescription(1);
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.PageSize.Width (DIPs): {pageDesc.PageSize.Width}");
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.PageSize.Height (DIPs): {pageDesc.PageSize.Height}");
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.DpiX: {pageDesc.DpiX}");
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.DpiY: {pageDesc.DpiY}");
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);
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated columns: {columns}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated rows: {rows}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated bitmapsPerPage: {bitmapsPerPage}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated pageCount: {pageCount}");
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();
}
System.Diagnostics.Debug.WriteLine("[PrintService] OnDocumentTaskOptionsChanged finished.");
}
private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender)
{
System.Diagnostics.Debug.WriteLine("[PrintService] LoadPDFPageBitmapsAsync starting...");
DisplayInformation displayInfo = DisplayInformation.GetForCurrentView();
float rawPixelsPerViewPixel = (float)displayInfo.RawPixelsPerViewPixel;
if (rawPixelsPerViewPixel == 0)
{
System.Diagnostics.Debug.WriteLine($"[PrintService] Warning: RawPixelsPerViewPixel was 0, defaulting to 1.0f");
rawPixelsPerViewPixel = 1.0f; // Sanity check
}
System.Diagnostics.Debug.WriteLine($"[PrintService] DisplayInformation.RawPixelsPerViewPixel: {rawPixelsPerViewPixel}");
System.Diagnostics.Debug.WriteLine($"[PrintService] DisplayInformation.LogicalDpi: {displayInfo.LogicalDpi}");
float printerDpi = sender.Dpi;
if (printerDpi == 0)
{
System.Diagnostics.Debug.WriteLine($"[PrintService] Warning: sender.Dpi (printerDpi) was 0, defaulting to 96.0f");
printerDpi = 96.0f; // Sanity check
}
System.Diagnostics.Debug.WriteLine($"[PrintService] sender.Dpi (CanvasPrintDocument.Dpi, used as PrinterDPI): {printerDpi}");
ClearBitmaps();
bitmaps ??= new List();
for (int i = 0; i < pdfDocument.PageCount; i++)
{
var page = pdfDocument.GetPage((uint)i);
System.Diagnostics.Debug.WriteLine($"[PrintService] Processing page.Index: {page.Index}");
System.Diagnostics.Debug.WriteLine($"[PrintService] page.Dimensions.MediaBox.Width (PDF points): {page.Dimensions.MediaBox.Width}");
System.Diagnostics.Debug.WriteLine($"[PrintService] page.Dimensions.MediaBox.Height (PDF points): {page.Dimensions.MediaBox.Height}");
double pageWidthInPoints = page.Dimensions.MediaBox.Width;
double pageHeightInPoints = page.Dimensions.MediaBox.Height;
double pageWidthInInches = pageWidthInPoints / 72.0;
double pageHeightInInches = pageHeightInPoints / 72.0;
// Calculate the desired pixel dimensions of the bitmap based on printer DPI
uint targetPixelWidth = (uint)(pageWidthInInches * printerDpi);
uint targetPixelHeight = (uint)(pageHeightInInches * printerDpi);
// Calculate DestinationWidth/Height for PdfPageRenderOptions in DIPs
PdfPageRenderOptions options = new PdfPageRenderOptions();
options.DestinationWidth = (uint)(targetPixelWidth / rawPixelsPerViewPixel);
options.DestinationHeight = (uint)(targetPixelHeight / rawPixelsPerViewPixel);
System.Diagnostics.Debug.WriteLine($"[PrintService] Page {i}, Calculated PdfPageRenderOptions.DestinationWidth (DIPs): {options.DestinationWidth}, DestinationHeight (DIPs): {options.DestinationHeight}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Page {i}, TargetPixelWidth: {targetPixelWidth}, TargetPixelHeight: {targetPixelHeight}");
using (var stream = new InMemoryRandomAccessStream())
{
await page.RenderToStreamAsync(stream, options); // Use the options
var bitmap = await CanvasBitmap.LoadAsync(sender, stream);
System.Diagnostics.Debug.WriteLine($"[PrintService] bitmap.SizeInPixels.Width: {bitmap.SizeInPixels.Width}");
System.Diagnostics.Debug.WriteLine($"[PrintService] bitmap.SizeInPixels.Height: {bitmap.SizeInPixels.Height}");
System.Diagnostics.Debug.WriteLine($"[PrintService] bitmap.Dpi: {bitmap.Dpi}");
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);
}
System.Diagnostics.Debug.WriteLine("[PrintService] LoadPDFPageBitmapsAsync finished.");
}
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++;
}
}
}
// --- START SIMULATION CODE ---
private async Task RunSimulationAsync(float simulatedRawPixelsPerViewPixel, string callingMethodName)
{
// --- Simulate LoadPDFPageBitmapsAsync ---
System.Diagnostics.Debug.WriteLine($"[PrintService] SIMULATING from {callingMethodName} for {simulatedRawPixelsPerViewPixel}");
System.Diagnostics.Debug.WriteLine("[PrintService] LoadPDFPageBitmapsAsync starting...");
// Mock DisplayInformation
float actualRawPixelsPerViewPixel = simulatedRawPixelsPerViewPixel; // Override
if (actualRawPixelsPerViewPixel == 0)
{
System.Diagnostics.Debug.WriteLine($"[PrintService] Warning: actualRawPixelsPerViewPixel was 0, defaulting to 1.0f");
actualRawPixelsPerViewPixel = 1.0f; // Sanity check
}
System.Diagnostics.Debug.WriteLine($"[PrintService] DisplayInformation.RawPixelsPerViewPixel: {actualRawPixelsPerViewPixel}");
// LogicalDpi is not directly used in calculations we are testing, so we can use a placeholder.
System.Diagnostics.Debug.WriteLine($"[PrintService] DisplayInformation.LogicalDpi: 96.0f (Simulated)");
// Mock CanvasPrintDocument sender for DPI
float printerDpi = 300.0f; // As per subtask
System.Diagnostics.Debug.WriteLine($"[PrintService] sender.Dpi (CanvasPrintDocument.Dpi, used as PrinterDPI): {printerDpi}");
// Clear any previous simulation state for bitmaps
// In a real scenario, ClearBitmaps() would be called. Here we just reset relevant fields.
this.bitmaps.Clear(); // Assuming 'bitmaps' is the List
this.largestBitmap = Vector2.Zero;
// Simulate pdfDocument.PageCount = 1 and loop once
int simulatedPageCount = 1;
for (int i = 0; i < simulatedPageCount; i++)
{
// Mock PdfPage
uint pageIndex = (uint)i;
double pageWidthInPoints = 612.0; // Letter size
double pageHeightInPoints = 792.0; // Letter size
System.Diagnostics.Debug.WriteLine($"[PrintService] Processing page.Index: {pageIndex}");
System.Diagnostics.Debug.WriteLine($"[PrintService] page.Dimensions.MediaBox.Width (PDF points): {pageWidthInPoints}");
System.Diagnostics.Debug.WriteLine($"[PrintService] page.Dimensions.MediaBox.Height (PDF points): {pageHeightInPoints}");
double pageWidthInInches = pageWidthInPoints / 72.0;
double pageHeightInInches = pageHeightInPoints / 72.0;
uint targetPixelWidth = (uint)(pageWidthInInches * printerDpi);
uint targetPixelHeight = (uint)(pageHeightInInches * printerDpi);
PdfPageRenderOptions options = new PdfPageRenderOptions();
options.DestinationWidth = (uint)(targetPixelWidth / actualRawPixelsPerViewPixel);
options.DestinationHeight = (uint)(targetPixelHeight / actualRawPixelsPerViewPixel);
System.Diagnostics.Debug.WriteLine($"[PrintService] Page {i}, Calculated PdfPageRenderOptions.DestinationWidth (DIPs): {options.DestinationWidth}, DestinationHeight (DIPs): {options.DestinationHeight}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Page {i}, TargetPixelWidth: {targetPixelWidth}, TargetPixelHeight: {targetPixelHeight}");
// Mock CanvasBitmap properties (as we can't actually render)
uint mockBitmapSizeInPixelsWidth = targetPixelWidth;
uint mockBitmapSizeInPixelsHeight = targetPixelHeight;
float mockBitmapDpi = printerDpi;
System.Diagnostics.Debug.WriteLine($"[PrintService] bitmap.SizeInPixels.Width: {mockBitmapSizeInPixelsWidth} (Simulated)");
System.Diagnostics.Debug.WriteLine($"[PrintService] bitmap.SizeInPixels.Height: {mockBitmapSizeInPixelsHeight} (Simulated)");
System.Diagnostics.Debug.WriteLine($"[PrintService] bitmap.Dpi: {mockBitmapDpi} (Simulated)");
// Simulate adding to bitmaps list and tracking largest
// We don't add a real CanvasBitmap, just update what's needed for OnDocumentTaskOptionsChanged
this.largestBitmap.X = Math.Max(this.largestBitmap.X, mockBitmapSizeInPixelsWidth);
this.largestBitmap.Y = Math.Max(this.largestBitmap.Y, mockBitmapSizeInPixelsHeight);
// bitmaps.Add(null); // Not adding real bitmaps
}
this.bitmapCount = simulatedPageCount; // Set based on simulation
System.Diagnostics.Debug.WriteLine("[PrintService] LoadPDFPageBitmapsAsync finished.");
// --- Simulate OnDocumentTaskOptionsChanged ---
System.Diagnostics.Debug.WriteLine("[PrintService] OnDocumentTaskOptionsChanged starting...");
// DisplayInformation logs already done in LoadPDFPageBitmapsAsync simulation part
// Mock PageDescription (from PrintTaskOptions)
// For Letter paper (8.5x11 inches) at 300 DPI:
// Pixel size = (8.5*300) x (11*300) = 2550 x 3300 pixels
// DIP size = (PixelSize / rawPixelsPerViewPixel)
float pageDescPageSizeWidth = (2550.0f / actualRawPixelsPerViewPixel);
float pageDescPageSizeHeight = (3300.0f / actualRawPixelsPerViewPixel);
float pageDescDpiX = 300.0f;
float pageDescDpiY = 300.0f;
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.PageSize.Width (DIPs): {pageDescPageSizeWidth} (Simulated)");
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.PageSize.Height (DIPs): {pageDescPageSizeHeight} (Simulated)");
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.DpiX: {pageDescDpiX} (Simulated)");
System.Diagnostics.Debug.WriteLine($"[PrintService] pageDesc.DpiY: {pageDescDpiY} (Simulated)");
this.pageSize = new Vector2(pageDescPageSizeWidth, pageDescPageSizeHeight);
// sender.InvalidatePreview(); // Cannot call
// Calculate the new layout
var printablePageSize = this.pageSize * 0.9f; // Assuming default behavior
this.imagePadding = new Vector2(64, 64); // Default from class
this.cellSize = this.largestBitmap + this.imagePadding;
var cellsPerPage = printablePageSize / this.cellSize;
this.columns = Math.Max(1, (int)Math.Floor(cellsPerPage.X));
this.rows = Math.Max(1, (int)Math.Floor(cellsPerPage.Y));
this.bitmapsPerPage = this.columns * this.rows;
this.pageCount = (int)Math.Ceiling(this.bitmapCount / (double)this.bitmapsPerPage);
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated columns: {this.columns}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated rows: {this.rows}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated bitmapsPerPage: {this.bitmapsPerPage}");
System.Diagnostics.Debug.WriteLine($"[PrintService] Calculated pageCount: {this.pageCount}");
// sender.SetPageCount((uint)this.pageCount); // Cannot call
System.Diagnostics.Debug.WriteLine("[PrintService] OnDocumentTaskOptionsChanged finished.");
System.Diagnostics.Debug.WriteLine($"[PrintService] SIMULATION END for {simulatedRawPixelsPerViewPixel}");
System.Diagnostics.Debug.WriteLine("---"); // Separator
}
public async Task SimulatePrintScalingAsync()
{
// Temporarily store and clear global state that might interfere
var originalBitmaps = new List(this.bitmaps);
var originalLargestBitmap = this.largestBitmap;
var originalPageSize = this.pageSize;
var originalCellSize = this.cellSize;
int originalBitmapCount = this.bitmapCount;
int originalColumns = this.columns;
int originalRows = this.rows;
int originalBitmapsPerPage = this.bitmapsPerPage;
int originalPageCount = this.pageCount;
await RunSimulationAsync(1.0f, nameof(SimulatePrintScalingAsync));
await RunSimulationAsync(1.25f, nameof(SimulatePrintScalingAsync));
await RunSimulationAsync(1.5f, nameof(SimulatePrintScalingAsync));
// Restore original state if necessary, though for this task it's just about logs
this.bitmaps = originalBitmaps;
this.largestBitmap = originalLargestBitmap;
this.pageSize = originalPageSize;
this.cellSize = originalCellSize;
this.bitmapCount = originalBitmapCount;
this.columns = originalColumns;
this.rows = originalRows;
this.bitmapsPerPage = originalBitmapsPerPage;
this.pageCount = originalPageCount;
}
// --- END SIMULATION CODE ---
}