Migrate mail printing to WinUI print preview

This commit is contained in:
Burak Kaan Köse
2026-04-11 15:07:22 +02:00
parent 24626d1c31
commit e206368801
12 changed files with 565 additions and 828 deletions
+499 -161
View File
@@ -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)
};
}