Migrate mail printing to WinUI print preview
This commit is contained in:
@@ -1,141 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
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 WinRT.Interop;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Printing;
|
||||
using DomainPrintCollation = Wino.Core.Domain.Enums.PrintCollation;
|
||||
using DomainPrintDuplex = Wino.Core.Domain.Enums.PrintDuplex;
|
||||
using DomainPrintMediaSize = Wino.Core.Domain.Enums.PrintMediaSize;
|
||||
using DomainPrintOrientation = Wino.Core.Domain.Enums.PrintOrientation;
|
||||
|
||||
namespace Wino.Mail.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// Printer service that uses the WinRT print preview UI with a WebView2-backed PDF render callback.
|
||||
/// </summary>
|
||||
|
||||
public class PrintService : IPrintService
|
||||
{
|
||||
private const float PdfRenderDpi = 300f;
|
||||
private const float DefaultDpi = 96f;
|
||||
|
||||
private TaskCompletionSource<PrintingResult>? _taskCompletionSource;
|
||||
private CanvasPrintDocument? printDocument;
|
||||
private PrintTask? printTask;
|
||||
private PdfDocument? pdfDocument;
|
||||
private CanvasPrintDocument? _printDocument;
|
||||
private PrintTask? _printTask;
|
||||
private PrintTaskOptionDetails? _printTaskOptionDetails;
|
||||
private PrintManager? _printManager;
|
||||
private PdfDocument? _pdfDocument;
|
||||
private Func<WebView2PrintSettingsModel, Task<Stream>>? _renderPdfStreamAsync;
|
||||
private WebView2PrintSettingsModel _currentRenderSettings = new();
|
||||
private string _printTitle = string.Empty;
|
||||
|
||||
private List<CanvasBitmap> bitmaps = new();
|
||||
private Vector2 largestBitmap;
|
||||
private Vector2 pageSize;
|
||||
private Vector2 imagePadding = new Vector2(64, 64);
|
||||
private Vector2 cellSize;
|
||||
private readonly List<CanvasBitmap> _bitmaps = new();
|
||||
private readonly List<int> _pageIndexesToPrint = new();
|
||||
private Vector2 _pageSize;
|
||||
private Windows.Foundation.Rect _imageableRect;
|
||||
private int _pagesPerSheet = 1;
|
||||
private int _columns = 1;
|
||||
private int _rows = 1;
|
||||
private int _sheetCount;
|
||||
|
||||
private int bitmapCount;
|
||||
private int columns;
|
||||
private int rows;
|
||||
private int bitmapsPerPage;
|
||||
private int pageCount = -1;
|
||||
|
||||
private PrintInformation? _currentPrintInformation;
|
||||
|
||||
public async Task<PrintingResult> PrintPdfFileAsync(string pdfFilePath, string printTitle)
|
||||
public async Task<PrintingResult> PrintAsync(nint windowHandle, string printTitle, Func<WebView2PrintSettingsModel, Task<Stream>> renderPdfStreamAsync)
|
||||
{
|
||||
if (windowHandle == IntPtr.Zero)
|
||||
return PrintingResult.Failed;
|
||||
|
||||
if (_taskCompletionSource != null)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(PrintingResult.Abandoned);
|
||||
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
|
||||
CleanupPrintSession();
|
||||
}
|
||||
|
||||
// Load the PDF file
|
||||
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfFilePath);
|
||||
pdfDocument = await PdfDocument.LoadFromFileAsync(file);
|
||||
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
|
||||
_renderPdfStreamAsync = renderPdfStreamAsync ?? throw new ArgumentNullException(nameof(renderPdfStreamAsync));
|
||||
_printTitle = printTitle ?? throw new ArgumentNullException(nameof(printTitle));
|
||||
_currentRenderSettings = new WebView2PrintSettingsModel();
|
||||
|
||||
_taskCompletionSource ??= new TaskCompletionSource<PrintingResult>();
|
||||
_printDocument = new CanvasPrintDocument();
|
||||
_printDocument.PrintTaskOptionsChanged += OnDocumentTaskOptionsChanged;
|
||||
_printDocument.Preview += OnDocumentPreview;
|
||||
_printDocument.Print += OnDocumentPrint;
|
||||
|
||||
_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;
|
||||
_printManager = PrintManagerInterop.GetForWindow(windowHandle);
|
||||
_printManager.PrintTaskRequested += OnPrintTaskRequested;
|
||||
|
||||
try
|
||||
{
|
||||
await PrintManager.ShowPrintUIAsync();
|
||||
|
||||
var result = await _taskCompletionSource.Task;
|
||||
|
||||
return result;
|
||||
await ReloadPdfDocumentAsync(_currentRenderSettings);
|
||||
await PrintManagerInterop.ShowPrintUIForWindowAsync(windowHandle);
|
||||
return await _taskCompletionSource.Task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose everything.
|
||||
UnregisterPrintManager(printManager);
|
||||
ClearBitmaps();
|
||||
UnregisterTask();
|
||||
DisposePDFDocument();
|
||||
|
||||
_taskCompletionSource = null;
|
||||
CleanupPrintSession();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposePDFDocument()
|
||||
private void CleanupPrintSession()
|
||||
{
|
||||
if (pdfDocument != null)
|
||||
var printManager = _printManager;
|
||||
_printManager = null;
|
||||
if (printManager != null)
|
||||
{
|
||||
pdfDocument = null;
|
||||
try
|
||||
{
|
||||
printManager.PrintTaskRequested -= OnPrintTaskRequested;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterTask()
|
||||
{
|
||||
var printTaskOptionDetails = _printTaskOptionDetails;
|
||||
_printTaskOptionDetails = null;
|
||||
if (printTaskOptionDetails != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
printTaskOptionDetails.OptionChanged -= OnPrintTaskOptionChanged;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var printTask = _printTask;
|
||||
_printTask = null;
|
||||
if (printTask != null)
|
||||
{
|
||||
printTask.Completed -= TaskCompleted;
|
||||
printTask = null;
|
||||
try
|
||||
{
|
||||
printTask.Completed -= OnPrintTaskCompleted;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterPrintManager(PrintManager manager)
|
||||
{
|
||||
manager.PrintTaskRequested -= PrintingExample_PrintTaskRequested;
|
||||
var printDocument = _printDocument;
|
||||
_printDocument = null;
|
||||
if (printDocument != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
printDocument.PrintTaskOptionsChanged -= OnDocumentTaskOptionsChanged;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
printDocument.Preview -= OnDocumentPreview;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
printDocument.Print -= OnDocumentPrint;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
_pdfDocument = null;
|
||||
ClearBitmaps();
|
||||
_pageIndexesToPrint.Clear();
|
||||
_taskCompletionSource = null;
|
||||
_renderPdfStreamAsync = null;
|
||||
_printTitle = string.Empty;
|
||||
}
|
||||
|
||||
private void ClearBitmaps()
|
||||
{
|
||||
foreach (var bitmap in bitmaps)
|
||||
foreach (var bitmap in _bitmaps)
|
||||
{
|
||||
bitmap.Dispose();
|
||||
}
|
||||
|
||||
bitmaps.Clear();
|
||||
_bitmaps.Clear();
|
||||
}
|
||||
|
||||
private void PrintingExample_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
|
||||
private void OnPrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
|
||||
{
|
||||
if (_currentPrintInformation == null) return;
|
||||
|
||||
printTask = args.Request.CreatePrintTask(_currentPrintInformation.PDFTitle, (createPrintTaskArgs) =>
|
||||
_printTask = args.Request.CreatePrintTask(_printTitle, createPrintTaskArgs =>
|
||||
{
|
||||
if (printDocument == null)
|
||||
if (_printDocument == null)
|
||||
return;
|
||||
|
||||
createPrintTaskArgs.SetSource(printDocument);
|
||||
createPrintTaskArgs.SetSource(_printDocument);
|
||||
});
|
||||
|
||||
printTask.Completed += TaskCompleted;
|
||||
_printTask.Completed += OnPrintTaskCompleted;
|
||||
|
||||
_printTaskOptionDetails = PrintTaskOptionDetails.GetFromPrintTaskOptions(_printTask.Options);
|
||||
_printTaskOptionDetails.DisplayedOptions.Clear();
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.Copies);
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.Orientation);
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.MediaSize);
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.Collation);
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.Duplex);
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.CustomPageRanges);
|
||||
TryAddDisplayedOption(StandardPrintTaskOptions.NUp);
|
||||
_printTaskOptionDetails.OptionChanged += OnPrintTaskOptionChanged;
|
||||
}
|
||||
|
||||
private void TaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args)
|
||||
=> _taskCompletionSource?.TrySetResult((PrintingResult)args.Completion);
|
||||
private void OnPrintTaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args)
|
||||
=> _taskCompletionSource?.TrySetResult(args.Completion switch
|
||||
{
|
||||
PrintTaskCompletion.Submitted => PrintingResult.Submitted,
|
||||
PrintTaskCompletion.Canceled => PrintingResult.Canceled,
|
||||
PrintTaskCompletion.Failed => PrintingResult.Failed,
|
||||
_ => PrintingResult.Abandoned
|
||||
});
|
||||
|
||||
private void OnPrintTaskOptionChanged(PrintTaskOptionDetails sender, PrintTaskOptionChangedEventArgs args)
|
||||
=> _printDocument?.InvalidatePreview();
|
||||
|
||||
private async void OnDocumentTaskOptionsChanged(CanvasPrintDocument sender, CanvasPrintTaskOptionsChangedEventArgs args)
|
||||
{
|
||||
@@ -143,47 +214,19 @@ public class PrintService : IPrintService
|
||||
|
||||
try
|
||||
{
|
||||
await LoadPDFPageBitmapsAsync(sender);
|
||||
var newSettings = CreateRenderSettings(args.PrintTaskOptions);
|
||||
|
||||
var pageDesc = args.PrintTaskOptions.GetPageDescription(1);
|
||||
var newPageSize = pageDesc.PageSize.ToVector2();
|
||||
|
||||
if (pageSize == newPageSize && pageCount != -1)
|
||||
if (ShouldReloadPdf(newSettings))
|
||||
{
|
||||
// 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;
|
||||
await ReloadPdfDocumentAsync(newSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentRenderSettings = newSettings;
|
||||
}
|
||||
|
||||
pageSize = newPageSize;
|
||||
UpdatePreviewLayout(args.PrintTaskOptions);
|
||||
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
|
||||
{
|
||||
@@ -191,85 +234,380 @@ public class PrintService : IPrintService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender)
|
||||
private async Task ReloadPdfDocumentAsync(WebView2PrintSettingsModel settings)
|
||||
{
|
||||
if (pdfDocument == null)
|
||||
return;
|
||||
if (_renderPdfStreamAsync == null)
|
||||
throw new InvalidOperationException("No PDF render callback is registered.");
|
||||
|
||||
await using var pdfStream = await _renderPdfStreamAsync(settings);
|
||||
var randomAccessStream = pdfStream.AsRandomAccessStream();
|
||||
|
||||
_pdfDocument = await PdfDocument.LoadFromStreamAsync(randomAccessStream);
|
||||
_currentRenderSettings = settings;
|
||||
|
||||
ClearBitmaps();
|
||||
|
||||
bitmaps ??= new List<CanvasBitmap>();
|
||||
if (_printDocument == null || _pdfDocument == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < pdfDocument.PageCount; i++)
|
||||
for (var 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);
|
||||
using var page = _pdfDocument.GetPage((uint)i);
|
||||
using var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
var renderOptions = CreateRenderOptions(page);
|
||||
await page.RenderToStreamAsync(stream, renderOptions);
|
||||
stream.Seek(0);
|
||||
var bitmap = await CanvasBitmap.LoadAsync(_printDocument, stream);
|
||||
_bitmaps.Add(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private static PdfPageRenderOptions CreateRenderOptions(PdfPage page)
|
||||
{
|
||||
var scale = PdfRenderDpi / DefaultDpi;
|
||||
var destinationWidth = Math.Max(1u, (uint)Math.Ceiling(page.Size.Width * scale));
|
||||
var destinationHeight = Math.Max(1u, (uint)Math.Ceiling(page.Size.Height * scale));
|
||||
|
||||
return new PdfPageRenderOptions
|
||||
{
|
||||
DestinationWidth = destinationWidth,
|
||||
DestinationHeight = destinationHeight
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdatePreviewLayout(PrintTaskOptions printTaskOptions)
|
||||
{
|
||||
if (_pdfDocument == null)
|
||||
{
|
||||
_sheetCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var pageDescription = printTaskOptions.GetPageDescription(1);
|
||||
_pageSize = pageDescription.PageSize.ToVector2();
|
||||
_imageableRect = pageDescription.ImageableRect;
|
||||
|
||||
_pagesPerSheet = GetPagesPerSheet();
|
||||
(_columns, _rows) = GetGrid(_pagesPerSheet);
|
||||
|
||||
_pageIndexesToPrint.Clear();
|
||||
_pageIndexesToPrint.AddRange(GetPageIndexesToPrint((int)_pdfDocument.PageCount));
|
||||
_sheetCount = Math.Max(1, (int)Math.Ceiling(_pageIndexesToPrint.Count / (double)_pagesPerSheet));
|
||||
|
||||
_printDocument?.SetPageCount((uint)_sheetCount);
|
||||
}
|
||||
|
||||
private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args)
|
||||
{
|
||||
var ds = args.DrawingSession;
|
||||
var pageNumber = args.PageNumber;
|
||||
|
||||
DrawPdfPage(sender, ds, pageNumber);
|
||||
}
|
||||
=> DrawSheet(args.DrawingSession, args.PageNumber);
|
||||
|
||||
private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args)
|
||||
{
|
||||
var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions);
|
||||
|
||||
if (pdfDocument == null)
|
||||
if (_pdfDocument == null || _sheetCount == 0)
|
||||
return;
|
||||
|
||||
int pageCountToPrint = (int)pdfDocument.PageCount;
|
||||
|
||||
for (uint i = 1; i <= pageCountToPrint; ++i)
|
||||
for (uint i = 1; i <= _sheetCount; i++)
|
||||
{
|
||||
using var ds = args.CreateDrawingSession();
|
||||
var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect;
|
||||
|
||||
DrawPdfPage(sender, ds, i);
|
||||
using var drawingSession = args.CreateDrawingSession();
|
||||
DrawSheet(drawingSession, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPdfPage(CanvasPrintDocument sender, CanvasDrawingSession ds, uint pageNumber)
|
||||
private void DrawSheet(CanvasDrawingSession drawingSession, uint pageNumber)
|
||||
{
|
||||
if (bitmaps.Count == 0) return;
|
||||
if (_bitmaps.Count == 0 || _pageIndexesToPrint.Count == 0)
|
||||
return;
|
||||
|
||||
var cellAcross = new Vector2(cellSize.X, 0);
|
||||
var cellDown = new Vector2(0, cellSize.Y);
|
||||
var printableSize = new Vector2((float)_imageableRect.Width, (float)_imageableRect.Height);
|
||||
var cellWidth = printableSize.X / _columns;
|
||||
var cellHeight = printableSize.Y / _rows;
|
||||
var topLeft = new Vector2((float)_imageableRect.X, (float)_imageableRect.Y);
|
||||
var pageIndex = ((int)pageNumber - 1) * _pagesPerSheet;
|
||||
|
||||
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 (var row = 0; row < _rows; row++)
|
||||
{
|
||||
for (int column = 0; column < columns; ++column)
|
||||
for (var 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;
|
||||
if (pageIndex >= _pageIndexesToPrint.Count)
|
||||
return;
|
||||
|
||||
ds.DrawImage(bitmapInfo, bitmapPos);
|
||||
|
||||
bitmapIndex++;
|
||||
var bitmap = _bitmaps[_pageIndexesToPrint[pageIndex]];
|
||||
var cellTopLeft = topLeft + new Vector2(cellWidth * column, cellHeight * row);
|
||||
DrawBitmapInCell(drawingSession, bitmap, cellTopLeft, new Vector2(cellWidth, cellHeight));
|
||||
pageIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawBitmapInCell(CanvasDrawingSession drawingSession, CanvasBitmap bitmap, Vector2 cellTopLeft, Vector2 cellSize)
|
||||
{
|
||||
var bitmapSize = bitmap.Size.ToVector2();
|
||||
var scale = Math.Min(cellSize.X / bitmapSize.X, cellSize.Y / bitmapSize.Y);
|
||||
var targetSize = bitmapSize * scale;
|
||||
var targetOffset = cellTopLeft + (cellSize - targetSize) / 2;
|
||||
drawingSession.DrawImage(bitmap, new Windows.Foundation.Rect(targetOffset.X, targetOffset.Y, targetSize.X, targetSize.Y));
|
||||
}
|
||||
|
||||
private WebView2PrintSettingsModel CreateRenderSettings(PrintTaskOptions printTaskOptions)
|
||||
=> new()
|
||||
{
|
||||
Orientation = GetOrientation(printTaskOptions),
|
||||
MediaSize = GetMediaSize(),
|
||||
PageRanges = GetPageRanges(),
|
||||
MarginTop = _currentRenderSettings.MarginTop,
|
||||
MarginBottom = _currentRenderSettings.MarginBottom,
|
||||
MarginLeft = _currentRenderSettings.MarginLeft,
|
||||
MarginRight = _currentRenderSettings.MarginRight,
|
||||
ShouldPrintBackgrounds = _currentRenderSettings.ShouldPrintBackgrounds,
|
||||
ShouldPrintSelectionOnly = _currentRenderSettings.ShouldPrintSelectionOnly,
|
||||
ShouldPrintHeaderAndFooter = _currentRenderSettings.ShouldPrintHeaderAndFooter,
|
||||
HeaderTitle = _currentRenderSettings.HeaderTitle,
|
||||
FooterUri = _currentRenderSettings.FooterUri,
|
||||
ScaleFactor = _currentRenderSettings.ScaleFactor
|
||||
};
|
||||
|
||||
private bool ShouldReloadPdf(WebView2PrintSettingsModel newSettings)
|
||||
=> newSettings.Orientation != _currentRenderSettings.Orientation
|
||||
|| newSettings.MediaSize != _currentRenderSettings.MediaSize
|
||||
|| newSettings.ShouldPrintBackgrounds != _currentRenderSettings.ShouldPrintBackgrounds
|
||||
|| newSettings.ShouldPrintHeaderAndFooter != _currentRenderSettings.ShouldPrintHeaderAndFooter
|
||||
|| !string.Equals(newSettings.HeaderTitle, _currentRenderSettings.HeaderTitle, StringComparison.Ordinal)
|
||||
|| !string.Equals(newSettings.FooterUri, _currentRenderSettings.FooterUri, StringComparison.Ordinal);
|
||||
|
||||
private int GetCopies()
|
||||
=> GetOptionValue(StandardPrintTaskOptions.Copies) as int? ?? 1;
|
||||
|
||||
private DomainPrintOrientation GetOrientation(PrintTaskOptions printTaskOptions)
|
||||
{
|
||||
var optionValue = GetOptionValue(StandardPrintTaskOptions.Orientation);
|
||||
if (optionValue is DomainPrintOrientation orientation)
|
||||
return orientation;
|
||||
|
||||
if (optionValue is string orientationString
|
||||
&& Enum.TryParse<DomainPrintOrientation>(orientationString, true, out var parsedOrientation))
|
||||
{
|
||||
return parsedOrientation;
|
||||
}
|
||||
|
||||
var pageDescription = printTaskOptions.GetPageDescription(1);
|
||||
return pageDescription.PageSize.Width >= pageDescription.PageSize.Height
|
||||
? DomainPrintOrientation.Landscape
|
||||
: DomainPrintOrientation.Portrait;
|
||||
}
|
||||
|
||||
private DomainPrintMediaSize GetMediaSize()
|
||||
{
|
||||
var optionValue = GetOptionValue(StandardPrintTaskOptions.MediaSize);
|
||||
if (optionValue is DomainPrintMediaSize mediaSize)
|
||||
return mediaSize;
|
||||
|
||||
if (optionValue is string mediaSizeString
|
||||
&& Enum.TryParse<DomainPrintMediaSize>(mediaSizeString, true, out var parsedMediaSize))
|
||||
{
|
||||
return parsedMediaSize;
|
||||
}
|
||||
|
||||
return DomainPrintMediaSize.Default;
|
||||
}
|
||||
|
||||
private DomainPrintCollation GetCollation()
|
||||
{
|
||||
var optionValue = GetOptionValue(StandardPrintTaskOptions.Collation);
|
||||
if (optionValue is DomainPrintCollation collation)
|
||||
return collation;
|
||||
|
||||
if (optionValue is string collationString
|
||||
&& Enum.TryParse<DomainPrintCollation>(collationString, true, out var parsedCollation))
|
||||
{
|
||||
return parsedCollation;
|
||||
}
|
||||
|
||||
return DomainPrintCollation.Default;
|
||||
}
|
||||
|
||||
private DomainPrintDuplex GetDuplex()
|
||||
{
|
||||
var optionValue = GetOptionValue(StandardPrintTaskOptions.Duplex);
|
||||
if (optionValue is DomainPrintDuplex duplex)
|
||||
return duplex;
|
||||
|
||||
if (optionValue is string duplexString)
|
||||
{
|
||||
return duplexString switch
|
||||
{
|
||||
nameof(DomainPrintDuplex.Simplex) => DomainPrintDuplex.Simplex,
|
||||
nameof(DomainPrintDuplex.DuplexShortEdge) => DomainPrintDuplex.DuplexShortEdge,
|
||||
nameof(DomainPrintDuplex.DuplexLongEdge) => DomainPrintDuplex.DuplexLongEdge,
|
||||
_ => DomainPrintDuplex.Default
|
||||
};
|
||||
}
|
||||
|
||||
return DomainPrintDuplex.Default;
|
||||
}
|
||||
|
||||
private int GetPagesPerSheet()
|
||||
{
|
||||
var optionValue = GetOptionValue(StandardPrintTaskOptions.NUp);
|
||||
if (optionValue is int pagesPerSheet)
|
||||
return NormalizePagesPerSheet(pagesPerSheet);
|
||||
|
||||
if (optionValue is string valueString)
|
||||
{
|
||||
if (int.TryParse(valueString, out var parsedPagesPerSheet))
|
||||
return NormalizePagesPerSheet(parsedPagesPerSheet);
|
||||
|
||||
return valueString switch
|
||||
{
|
||||
"TwoUp" => 2,
|
||||
"FourUp" => 4,
|
||||
"SixUp" => 6,
|
||||
"NineUp" => 9,
|
||||
"SixteenUp" => 16,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private string GetPageRanges()
|
||||
{
|
||||
var optionValue = GetOptionValue(StandardPrintTaskOptions.CustomPageRanges);
|
||||
return optionValue?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private object? GetOptionValue(string optionId)
|
||||
{
|
||||
if (_printTaskOptionDetails == null)
|
||||
return null;
|
||||
|
||||
if (!TryGetOption(optionId, out var option))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return option switch
|
||||
{
|
||||
PrintCopiesOptionDetails copies => copies.Value,
|
||||
PrintPageRangeOptionDetails pageRanges => pageRanges.Value,
|
||||
IPrintItemListOptionDetails itemList => itemList.Value,
|
||||
IPrintNumberOptionDetails number => number.Value,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAddDisplayedOption(string optionId)
|
||||
{
|
||||
if (_printTaskOptionDetails == null)
|
||||
return;
|
||||
|
||||
if (TryGetOption(optionId, out _))
|
||||
{
|
||||
_printTaskOptionDetails.DisplayedOptions.Add(optionId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetOption(string optionId, out IPrintOptionDetails? option)
|
||||
{
|
||||
option = null;
|
||||
|
||||
if (_printTaskOptionDetails == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (_printTaskOptionDetails.Options.TryGetValue(optionId, out option))
|
||||
return option != null;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<int> GetPageIndexesToPrint(int totalPageCount)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_currentRenderSettings.PageRanges))
|
||||
return GetAllPages(totalPageCount);
|
||||
|
||||
var pageIndexes = new List<int>();
|
||||
var tokens = _currentRenderSettings.PageRanges.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (token.Contains('-'))
|
||||
{
|
||||
var bounds = token.Split('-', StringSplitOptions.TrimEntries);
|
||||
if (bounds.Length != 2
|
||||
|| !int.TryParse(bounds[0], out var start)
|
||||
|| !int.TryParse(bounds[1], out var end))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
{
|
||||
(start, end) = (end, start);
|
||||
}
|
||||
|
||||
for (var i = start; i <= end; i++)
|
||||
{
|
||||
AddPageIndex(pageIndexes, i, totalPageCount);
|
||||
}
|
||||
}
|
||||
else if (int.TryParse(token, out var pageNumber))
|
||||
{
|
||||
AddPageIndex(pageIndexes, pageNumber, totalPageCount);
|
||||
}
|
||||
}
|
||||
|
||||
return pageIndexes.Count > 0
|
||||
? pageIndexes
|
||||
: GetAllPages(totalPageCount);
|
||||
}
|
||||
|
||||
private static IEnumerable<int> GetAllPages(int totalPageCount)
|
||||
{
|
||||
for (var i = 0; i < totalPageCount; i++)
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddPageIndex(ICollection<int> pageIndexes, int pageNumber, int totalPageCount)
|
||||
{
|
||||
var zeroBasedIndex = pageNumber - 1;
|
||||
if (zeroBasedIndex >= 0 && zeroBasedIndex < totalPageCount)
|
||||
{
|
||||
pageIndexes.Add(zeroBasedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static int NormalizePagesPerSheet(int pagesPerSheet)
|
||||
=> pagesPerSheet switch
|
||||
{
|
||||
2 or 4 or 6 or 9 or 16 => pagesPerSheet,
|
||||
_ => 1
|
||||
};
|
||||
|
||||
private static (int Columns, int Rows) GetGrid(int pagesPerSheet)
|
||||
=> pagesPerSheet switch
|
||||
{
|
||||
2 => (1, 2),
|
||||
4 => (2, 2),
|
||||
6 => (2, 3),
|
||||
9 => (3, 3),
|
||||
16 => (4, 4),
|
||||
_ => (1, 1)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user