Migrate mail printing to WinUI print preview
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
using Wino.Core.Domain.Models.Common;
|
using Wino.Core.Domain.Models.Common;
|
||||||
using Wino.Core.Domain.Models.Printing;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
@@ -30,5 +29,4 @@ public interface IDialogServiceBase
|
|||||||
Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters);
|
Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters);
|
||||||
Task<List<PickedFileMetadata>> PickFilesMetadataAsync(params object[] typeFilters);
|
Task<List<PickedFileMetadata>> PickFilesMetadataAsync(params object[] typeFilters);
|
||||||
Task<string> PickFilePathAsync(string saveFileName);
|
Task<string> PickFilePathAsync(string saveFileName);
|
||||||
Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(WebView2PrintSettingsModel initialSettings = null);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Models.Printing;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
public interface IPrintService
|
public interface IPrintService
|
||||||
{
|
{
|
||||||
Task<PrintingResult> PrintPdfFileAsync(string pdfFilePath, string printTitle);
|
Task<PrintingResult> PrintAsync(nint windowHandle, string printTitle, Func<WebView2PrintSettingsModel, Task<Stream>> renderPdfStreamAsync);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Printing;
|
|
||||||
|
|
||||||
public class PrintInformation
|
|
||||||
{
|
|
||||||
public PrintInformation(string pDFFilePath, string pDFTitle)
|
|
||||||
{
|
|
||||||
PDFFilePath = pDFFilePath ?? throw new ArgumentNullException(nameof(pDFFilePath));
|
|
||||||
PDFTitle = pDFTitle ?? throw new ArgumentNullException(nameof(pDFTitle));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string PDFFilePath { get; }
|
|
||||||
public string PDFTitle { get; }
|
|
||||||
}
|
|
||||||
@@ -58,9 +58,9 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
private MimeMessageInformation initializedMimeMessageInformation = null;
|
private MimeMessageInformation initializedMimeMessageInformation = null;
|
||||||
|
|
||||||
// Func to get WebView2 to save current HTML as PDF to given location.
|
// Func to get WebView2 to save current HTML as PDF to given location.
|
||||||
// Used in 'Save as' and 'Print' functionality.
|
// Used in 'Save as' functionality.
|
||||||
public Func<string, Task<bool>> SaveHTMLasPDFFunc { get; set; }
|
public Func<string, Task<bool>> SaveHTMLasPDFFunc { get; set; }
|
||||||
public Func<WebView2PrintSettingsModel, Task<PrintingResult>> DirectPrintFuncAsync { get; set; }
|
public Func<WebView2PrintSettingsModel, Task<Stream>> RenderPdfStreamFuncAsync { get; set; }
|
||||||
public Func<string, Task> RenderHtmlAsyncFunc { get; set; }
|
public Func<string, Task> RenderHtmlAsyncFunc { get; set; }
|
||||||
public Func<Task> ClearRenderedHtmlAsyncFunc { get; set; }
|
public Func<Task> ClearRenderedHtmlAsyncFunc { get; set; }
|
||||||
|
|
||||||
@@ -275,17 +275,17 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
else if (operation == MailOperation.Print)
|
else if (operation == MailOperation.Print)
|
||||||
{
|
{
|
||||||
var settings = await _dialogService.ShowPrintDialogAsync();
|
var printingResult = await PrintAsync();
|
||||||
|
|
||||||
if (settings == null) return;
|
|
||||||
|
|
||||||
var printingResult = await DirectPrintFuncAsync.Invoke(settings);
|
|
||||||
|
|
||||||
// TODO: More detailed printing result handling.
|
// TODO: More detailed printing result handling.
|
||||||
if (printingResult == PrintingResult.Submitted)
|
if (printingResult == PrintingResult.Submitted)
|
||||||
{
|
{
|
||||||
_dialogService.InfoBarMessage(Translator.DialogMessage_PrintingSuccessTitle, Translator.DialogMessage_PrintingSuccessMessage, InfoBarMessageType.Success);
|
_dialogService.InfoBarMessage(Translator.DialogMessage_PrintingSuccessTitle, Translator.DialogMessage_PrintingSuccessMessage, InfoBarMessageType.Success);
|
||||||
}
|
}
|
||||||
|
else if (printingResult == PrintingResult.Canceled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
else if (printingResult == PrintingResult.Failed)
|
else if (printingResult == PrintingResult.Failed)
|
||||||
{
|
{
|
||||||
_dialogService.InfoBarMessage(Translator.DialogMessage_PrintingFailedTitle, Translator.DialogMessage_PrintingFailedMessage, InfoBarMessageType.Error);
|
_dialogService.InfoBarMessage(Translator.DialogMessage_PrintingFailedTitle, Translator.DialogMessage_PrintingFailedMessage, InfoBarMessageType.Error);
|
||||||
@@ -784,6 +784,22 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<PrintingResult> PrintAsync()
|
||||||
|
{
|
||||||
|
if (RenderPdfStreamFuncAsync == null)
|
||||||
|
return PrintingResult.Failed;
|
||||||
|
|
||||||
|
var windowHandle = NativeAppService.GetCoreWindowHwnd();
|
||||||
|
if (windowHandle == IntPtr.Zero)
|
||||||
|
return PrintingResult.Failed;
|
||||||
|
|
||||||
|
var printTitle = string.IsNullOrWhiteSpace(Subject)
|
||||||
|
? Translator.MailItemNoSubject
|
||||||
|
: Subject;
|
||||||
|
|
||||||
|
return await PrintService.PrintAsync(windowHandle, printTitle, RenderPdfStreamFuncAsync);
|
||||||
|
}
|
||||||
|
|
||||||
// Returns created file path.
|
// Returns created file path.
|
||||||
private async Task<string> SaveAttachmentInternalAsync(MailAttachmentViewModel attachmentViewModel, string saveFolderPath)
|
private async Task<string> SaveAttachmentInternalAsync(MailAttachmentViewModel attachmentViewModel, string saveFolderPath)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<ContentDialog
|
|
||||||
x:Class="Wino.Mail.WinUI.Dialogs.PrintDialog"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:printing="using:Wino.Core.Domain.Models.Printing"
|
|
||||||
Title="Print Settings"
|
|
||||||
MinWidth="400"
|
|
||||||
MinHeight="300"
|
|
||||||
DefaultButton="Primary"
|
|
||||||
Loaded="PrintDialog_Loaded"
|
|
||||||
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
|
|
||||||
PrimaryButtonText="Print"
|
|
||||||
SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
|
|
||||||
SecondaryButtonText="Cancel"
|
|
||||||
Style="{StaticResource WinoDialogStyle}"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<StackPanel Spacing="20">
|
|
||||||
|
|
||||||
<!-- Printer Selection -->
|
|
||||||
<ComboBox
|
|
||||||
x:Name="PrinterComboBox"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Header="Printer"
|
|
||||||
SelectedItem="{x:Bind PrintSettings.PrinterName, Mode=TwoWay}"
|
|
||||||
SelectionChanged="PrinterComboBox_SelectionChanged" />
|
|
||||||
|
|
||||||
<!-- Copies -->
|
|
||||||
<NumberBox
|
|
||||||
Header="Copies"
|
|
||||||
Maximum="999"
|
|
||||||
Minimum="1"
|
|
||||||
SpinButtonPlacementMode="Inline"
|
|
||||||
Value="{x:Bind PrintSettings.Copies, Mode=TwoWay}" />
|
|
||||||
|
|
||||||
<!-- Orientation -->
|
|
||||||
<RadioButtons
|
|
||||||
x:Name="OrientationRadioButtons"
|
|
||||||
Header="Orientation"
|
|
||||||
SelectionChanged="OrientationRadio_SelectionChanged">
|
|
||||||
<RadioButton Content="Portrait" />
|
|
||||||
<RadioButton Content="Landscape" />
|
|
||||||
</RadioButtons>
|
|
||||||
|
|
||||||
<!-- Print Options -->
|
|
||||||
<StackPanel Spacing="8">
|
|
||||||
<TextBlock
|
|
||||||
Margin="0,0,0,8"
|
|
||||||
Style="{ThemeResource BodyStrongTextBlockStyle}"
|
|
||||||
Text="Options" />
|
|
||||||
<Border
|
|
||||||
Padding="16,12"
|
|
||||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
|
||||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
|
||||||
BorderThickness="1"
|
|
||||||
CornerRadius="6">
|
|
||||||
<StackPanel Spacing="8">
|
|
||||||
<CheckBox Content="Print backgrounds" IsChecked="{x:Bind PrintSettings.ShouldPrintBackgrounds, Mode=TwoWay}" />
|
|
||||||
<CheckBox Content="Print headers and footers" IsChecked="{x:Bind PrintSettings.ShouldPrintHeaderAndFooter, Mode=TwoWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</ContentDialog>
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Serilog;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Models.Printing;
|
|
||||||
using Wino.Mail.WinUI.Helpers;
|
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Dialogs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom print dialog for configuring WebView2 print settings.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class PrintDialog : ContentDialog
|
|
||||||
{
|
|
||||||
public WebView2PrintSettingsModel PrintSettings { get; set; } = new WebView2PrintSettingsModel();
|
|
||||||
|
|
||||||
public PrintDialog()
|
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the dialog with existing print settings.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="printSettings">The initial print settings to load.</param>
|
|
||||||
public PrintDialog(WebView2PrintSettingsModel printSettings = default!)
|
|
||||||
{
|
|
||||||
if (printSettings != null) PrintSettings = printSettings;
|
|
||||||
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrintDialog_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => LoadSettingsToUI(PrintSettings);
|
|
||||||
|
|
||||||
private void OrientationRadio_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is RadioButtons radioButtons)
|
|
||||||
{
|
|
||||||
PrintSettings.Orientation = (PrintOrientation)radioButtons.SelectedIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrinterComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is ComboBox comboBox && comboBox.SelectedItem != null)
|
|
||||||
{
|
|
||||||
PrintSettings.PrinterName = comboBox.SelectedItem.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the list of available printers for the dialog.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="printers">List of available printer names.</param>
|
|
||||||
public void SetAvailablePrinters(IEnumerable<string> printers)
|
|
||||||
{
|
|
||||||
var printerList = printers?.ToList() ?? new List<string>();
|
|
||||||
|
|
||||||
if (this.FindName("PrinterComboBox") is ComboBox printerComboBox)
|
|
||||||
{
|
|
||||||
printerComboBox.ItemsSource = printerList;
|
|
||||||
|
|
||||||
if (printerList.Any())
|
|
||||||
{
|
|
||||||
// Set to first printer or to the one in settings
|
|
||||||
var targetPrinter = !string.IsNullOrEmpty(PrintSettings.PrinterName)
|
|
||||||
? PrintSettings.PrinterName
|
|
||||||
: printerList.First();
|
|
||||||
|
|
||||||
var index = printerList.IndexOf(targetPrinter);
|
|
||||||
printerComboBox.SelectedIndex = index >= 0 ? index : 0;
|
|
||||||
|
|
||||||
// Update the settings model with the selected printer
|
|
||||||
PrintSettings.PrinterName = printerComboBox.SelectedItem?.ToString() ?? string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads available printers asynchronously and sets them in the dialog.
|
|
||||||
/// </summary>
|
|
||||||
public async Task LoadAvailablePrintersAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var printers = await Task.Run(() =>
|
|
||||||
{
|
|
||||||
return InstalledPrinterHelper.GetInstalledPrinters().AsEnumerable();
|
|
||||||
});
|
|
||||||
|
|
||||||
SetAvailablePrinters(printers);
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
// Log the exception if logging is available
|
|
||||||
Log.Error(ex, "Error getting available printers");
|
|
||||||
|
|
||||||
// Set empty list if printer discovery fails
|
|
||||||
SetAvailablePrinters(Enumerable.Empty<string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadSettingsToUI(WebView2PrintSettingsModel settings)
|
|
||||||
{
|
|
||||||
if (settings == null) return;
|
|
||||||
|
|
||||||
// Only handle orientation manually since other properties are bound via x:Bind
|
|
||||||
if (this.FindName("OrientationRadioButtons") is RadioButtons orientationRadio)
|
|
||||||
{
|
|
||||||
orientationRadio.SelectedIndex = (int)settings.Orientation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSettingsFromUI()
|
|
||||||
{
|
|
||||||
// Most properties are bound via x:Bind, only handle orientation manually
|
|
||||||
if (this.FindName("OrientationRadioButtons") is RadioButtons orientationRadio)
|
|
||||||
{
|
|
||||||
PrintSettings.Orientation = (PrintOrientation)orientationRadio.SelectedIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also update printer name from ComboBox since it uses ItemsSource binding
|
|
||||||
if (this.FindName("PrinterComboBox") is ComboBox printerComboBox &&
|
|
||||||
printerComboBox.SelectedItem != null)
|
|
||||||
{
|
|
||||||
PrintSettings.PrinterName = printerComboBox.SelectedItem.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates the current print settings before closing the dialog.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if settings are valid, false otherwise.</returns>
|
|
||||||
private bool ValidateSettings()
|
|
||||||
{
|
|
||||||
// Check if a printer is selected
|
|
||||||
if (this.FindName("PrinterComboBox") is ComboBox printerComboBox &&
|
|
||||||
printerComboBox.SelectedItem == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copies validation is handled by the bound property with validation in the model
|
|
||||||
if (PrintSettings.Copies <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
|
||||||
{
|
|
||||||
// Update settings from UI before validation
|
|
||||||
UpdateSettingsFromUI();
|
|
||||||
|
|
||||||
// Validate settings before closing
|
|
||||||
if (!ValidateSettings())
|
|
||||||
{
|
|
||||||
args.Cancel = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
|
||||||
{
|
|
||||||
// Cancel was clicked, no validation needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -178,6 +178,33 @@ public static class PrintSettingsExtensions
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a CoreWebView2PrintSettings object containing only document-safe settings for PDF generation.
|
||||||
|
/// Printer job options such as copies, duplex, printer name, N-up, and page ranges stay in the WinRT print session.
|
||||||
|
/// </summary>
|
||||||
|
public static CoreWebView2PrintSettings ToCoreWebView2PdfRenderSettings(
|
||||||
|
this WebView2PrintSettingsModel model,
|
||||||
|
CoreWebView2Environment environment)
|
||||||
|
{
|
||||||
|
var settings = environment.CreatePrintSettings();
|
||||||
|
|
||||||
|
settings.Orientation = model.Orientation.ToCoreWebView2Orientation();
|
||||||
|
settings.ColorMode = model.ColorMode.ToCoreWebView2ColorMode();
|
||||||
|
settings.MediaSize = model.MediaSize.ToCoreWebView2MediaSize();
|
||||||
|
settings.MarginTop = model.MarginTop;
|
||||||
|
settings.MarginBottom = model.MarginBottom;
|
||||||
|
settings.MarginLeft = model.MarginLeft;
|
||||||
|
settings.MarginRight = model.MarginRight;
|
||||||
|
settings.ShouldPrintBackgrounds = model.ShouldPrintBackgrounds;
|
||||||
|
settings.ShouldPrintSelectionOnly = model.ShouldPrintSelectionOnly;
|
||||||
|
settings.ShouldPrintHeaderAndFooter = model.ShouldPrintHeaderAndFooter;
|
||||||
|
settings.HeaderTitle = model.HeaderTitle;
|
||||||
|
settings.FooterUri = model.FooterUri;
|
||||||
|
settings.ScaleFactor = model.ScaleFactor;
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a WebView2PrintSettingsModel from a CoreWebView2PrintSettings object.
|
/// Updates a WebView2PrintSettingsModel from a CoreWebView2PrintSettings object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -209,4 +236,4 @@ public static class PrintSettingsExtensions
|
|||||||
model.PagesPerSide = settings.PagesPerSide;
|
model.PagesPerSide = settings.PagesPerSide;
|
||||||
model.PageRanges = settings.PageRanges ?? string.Empty;
|
model.PageRanges = settings.PageRanges ?? string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Helpers;
|
|
||||||
|
|
||||||
internal static partial class InstalledPrinterHelper
|
|
||||||
{
|
|
||||||
private const int PrinterEnumLocal = 0x00000002;
|
|
||||||
private const int PrinterEnumConnections = 0x00000004;
|
|
||||||
|
|
||||||
public static IReadOnlyList<string> GetInstalledPrinters()
|
|
||||||
{
|
|
||||||
var flags = PrinterEnumLocal | PrinterEnumConnections;
|
|
||||||
|
|
||||||
if (!EnumPrinters(flags, null, 4, IntPtr.Zero, 0, out var bytesNeeded, out _))
|
|
||||||
{
|
|
||||||
var error = Marshal.GetLastWin32Error();
|
|
||||||
if (error != 122 || bytesNeeded <= 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"EnumPrinters failed to query buffer size. Win32 error: {error}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var printerBuffer = Marshal.AllocHGlobal(bytesNeeded);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!EnumPrinters(flags, null, 4, printerBuffer, bytesNeeded, out _, out var printerCount))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"EnumPrinters failed to enumerate printers. Win32 error: {Marshal.GetLastWin32Error()}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var printers = new List<string>(printerCount);
|
|
||||||
var structSize = Marshal.SizeOf<PrinterInfo4>();
|
|
||||||
|
|
||||||
for (var i = 0; i < printerCount; i++)
|
|
||||||
{
|
|
||||||
var current = IntPtr.Add(printerBuffer, i * structSize);
|
|
||||||
var info = Marshal.PtrToStructure<PrinterInfo4>(current);
|
|
||||||
if (!string.IsNullOrWhiteSpace(info.PrinterName))
|
|
||||||
{
|
|
||||||
printers.Add(info.PrinterName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return printers;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(printerBuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[LibraryImport("winspool.drv", EntryPoint = "EnumPrintersW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
|
||||||
private static partial bool EnumPrinters(
|
|
||||||
int flags,
|
|
||||||
string? name,
|
|
||||||
int level,
|
|
||||||
IntPtr pPrinterEnum,
|
|
||||||
int cbBuf,
|
|
||||||
out int pcbNeeded,
|
|
||||||
out int pcReturned);
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
private struct PrinterInfo4
|
|
||||||
{
|
|
||||||
public nint PrinterNamePointer;
|
|
||||||
public nint ServerNamePointer;
|
|
||||||
public int Attributes;
|
|
||||||
|
|
||||||
public string? PrinterName => Marshal.PtrToStringUni(PrinterNamePointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Models.Printing;
|
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Dialogs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ViewModel for the PrintDialog that handles data binding and state management.
|
|
||||||
/// </summary>
|
|
||||||
public class PrintDialogViewModel : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private List<string> _availablePrinters = new();
|
|
||||||
private bool _isCustomPageRange = false;
|
|
||||||
private WebView2PrintSettingsModel _printSettings = new();
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
public PrintDialogViewModel()
|
|
||||||
{
|
|
||||||
// Initialize default values
|
|
||||||
PrintSettings.PropertyChanged += OnPrintSettingsChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The print settings model that will be configured by the dialog.
|
|
||||||
/// </summary>
|
|
||||||
public WebView2PrintSettingsModel PrintSettings
|
|
||||||
{
|
|
||||||
get => _printSettings;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_printSettings != value)
|
|
||||||
{
|
|
||||||
if (_printSettings != null)
|
|
||||||
_printSettings.PropertyChanged -= OnPrintSettingsChanged;
|
|
||||||
|
|
||||||
_printSettings = value;
|
|
||||||
|
|
||||||
if (_printSettings != null)
|
|
||||||
_printSettings.PropertyChanged += OnPrintSettingsChanged;
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(PrintSettings));
|
|
||||||
UpdateDerivedProperties();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of available printers.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> AvailablePrinters
|
|
||||||
{
|
|
||||||
get => _availablePrinters;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_availablePrinters != value)
|
|
||||||
{
|
|
||||||
_availablePrinters = value ?? new List<string>();
|
|
||||||
OnPropertyChanged(nameof(AvailablePrinters));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the orientation radio buttons.
|
|
||||||
/// </summary>
|
|
||||||
public int OrientationIndex
|
|
||||||
{
|
|
||||||
get => (int)PrintSettings.Orientation;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value >= 0 && value <= 1)
|
|
||||||
{
|
|
||||||
PrintSettings.Orientation = (PrintOrientation)value;
|
|
||||||
OnPropertyChanged(nameof(OrientationIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the color mode radio buttons.
|
|
||||||
/// </summary>
|
|
||||||
public int ColorModeIndex
|
|
||||||
{
|
|
||||||
get => (int)PrintSettings.ColorMode;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value >= 0 && value <= 2)
|
|
||||||
{
|
|
||||||
PrintSettings.ColorMode = (PrintColorMode)value;
|
|
||||||
OnPropertyChanged(nameof(ColorModeIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the collation radio buttons.
|
|
||||||
/// </summary>
|
|
||||||
public int CollationIndex
|
|
||||||
{
|
|
||||||
get => (int)PrintSettings.Collation;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value >= 0 && value <= 2)
|
|
||||||
{
|
|
||||||
PrintSettings.Collation = (PrintCollation)value;
|
|
||||||
OnPropertyChanged(nameof(CollationIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the duplex radio buttons.
|
|
||||||
/// </summary>
|
|
||||||
public int DuplexIndex
|
|
||||||
{
|
|
||||||
get => (int)PrintSettings.Duplex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value >= 0 && value <= 3)
|
|
||||||
{
|
|
||||||
PrintSettings.Duplex = (PrintDuplex)value;
|
|
||||||
OnPropertyChanged(nameof(DuplexIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the media size combo box.
|
|
||||||
/// </summary>
|
|
||||||
public int MediaSizeIndex
|
|
||||||
{
|
|
||||||
get => (int)PrintSettings.MediaSize;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value >= 0 && value <= 9)
|
|
||||||
{
|
|
||||||
PrintSettings.MediaSize = (PrintMediaSize)value;
|
|
||||||
OnPropertyChanged(nameof(MediaSizeIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the pages per side combo box.
|
|
||||||
/// </summary>
|
|
||||||
public int PagesPerSideIndex
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var validValues = new[] { 1, 2, 4, 6, 9, 16 };
|
|
||||||
return Array.IndexOf(validValues, PrintSettings.PagesPerSide);
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
var validValues = new[] { 1, 2, 4, 6, 9, 16 };
|
|
||||||
if (value >= 0 && value < validValues.Length)
|
|
||||||
{
|
|
||||||
PrintSettings.PagesPerSide = validValues[value];
|
|
||||||
OnPropertyChanged(nameof(PagesPerSideIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Index for the page range option (0 = All pages, 1 = Custom range).
|
|
||||||
/// </summary>
|
|
||||||
public int PageRangeOptionIndex
|
|
||||||
{
|
|
||||||
get => IsCustomPageRange ? 1 : 0;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
IsCustomPageRange = value == 1;
|
|
||||||
if (!IsCustomPageRange)
|
|
||||||
{
|
|
||||||
PrintSettings.PageRanges = string.Empty;
|
|
||||||
}
|
|
||||||
OnPropertyChanged(nameof(PageRangeOptionIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether custom page range is selected.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsCustomPageRange
|
|
||||||
{
|
|
||||||
get => _isCustomPageRange;
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
if (_isCustomPageRange != value)
|
|
||||||
{
|
|
||||||
_isCustomPageRange = value;
|
|
||||||
OnPropertyChanged(nameof(IsCustomPageRange));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scale factor as percentage text for display.
|
|
||||||
/// </summary>
|
|
||||||
public string ScalePercentageText => $"{(int)(PrintSettings.ScaleFactor * 100)}%";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the dialog with the provided print settings.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="printSettings">The initial print settings.</param>
|
|
||||||
public void Initialize(WebView2PrintSettingsModel printSettings = default!)
|
|
||||||
{
|
|
||||||
if (printSettings != null)
|
|
||||||
{
|
|
||||||
PrintSettings = printSettings;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PrintSettings = new WebView2PrintSettingsModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateDerivedProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the list of available printers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="printers">List of printer names.</param>
|
|
||||||
public void SetAvailablePrinters(IEnumerable<string> printers)
|
|
||||||
{
|
|
||||||
AvailablePrinters = printers?.ToList() ?? new List<string>();
|
|
||||||
|
|
||||||
// If current printer is not in the list, select the first one
|
|
||||||
if (AvailablePrinters.Any() && !AvailablePrinters.Contains(PrintSettings.PrinterName))
|
|
||||||
{
|
|
||||||
PrintSettings.PrinterName = AvailablePrinters.First();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPrintSettingsChanged(object? sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.PropertyName == nameof(WebView2PrintSettingsModel.ScaleFactor))
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(ScalePercentageText));
|
|
||||||
}
|
|
||||||
else if (e.PropertyName == nameof(WebView2PrintSettingsModel.PageRanges))
|
|
||||||
{
|
|
||||||
// Update custom page range flag based on whether page ranges is empty
|
|
||||||
if (!string.IsNullOrWhiteSpace(PrintSettings.PageRanges))
|
|
||||||
{
|
|
||||||
IsCustomPageRange = true;
|
|
||||||
OnPropertyChanged(nameof(PageRangeOptionIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateDerivedProperties()
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(OrientationIndex));
|
|
||||||
OnPropertyChanged(nameof(ColorModeIndex));
|
|
||||||
OnPropertyChanged(nameof(CollationIndex));
|
|
||||||
OnPropertyChanged(nameof(DuplexIndex));
|
|
||||||
OnPropertyChanged(nameof(MediaSizeIndex));
|
|
||||||
OnPropertyChanged(nameof(PagesPerSideIndex));
|
|
||||||
OnPropertyChanged(nameof(PageRangeOptionIndex));
|
|
||||||
OnPropertyChanged(nameof(ScalePercentageText));
|
|
||||||
|
|
||||||
// Update custom page range based on current page ranges value
|
|
||||||
IsCustomPageRange = !string.IsNullOrWhiteSpace(PrintSettings.PageRanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnPropertyChanged(string propertyName)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
using Wino.Core.Domain.Models.Common;
|
using Wino.Core.Domain.Models.Common;
|
||||||
using Wino.Core.Domain.Models.Printing;
|
|
||||||
using Wino.Dialogs;
|
using Wino.Dialogs;
|
||||||
using Wino.Mail.WinUI.Dialogs;
|
using Wino.Mail.WinUI.Dialogs;
|
||||||
using Wino.Mail.WinUI.Extensions;
|
using Wino.Mail.WinUI.Extensions;
|
||||||
@@ -355,37 +354,4 @@ public class DialogServiceBase : IDialogServiceBase
|
|||||||
return dialog.Result ?? null!;
|
return dialog.Result ?? null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(WebView2PrintSettingsModel initialSettings = default!)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Create the print dialog
|
|
||||||
var dialog = initialSettings != null
|
|
||||||
? new PrintDialog(initialSettings)
|
|
||||||
: new PrintDialog();
|
|
||||||
|
|
||||||
// Set the XamlRoot for proper display
|
|
||||||
dialog.XamlRoot = GetXamlRoot();
|
|
||||||
|
|
||||||
// Get available printers asynchronously when the dialog is loaded
|
|
||||||
dialog.Loaded += async (sender, e) =>
|
|
||||||
{
|
|
||||||
await dialog.LoadAvailablePrintersAsync();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show the dialog
|
|
||||||
var result = await HandleDialogPresentationAsync(dialog);
|
|
||||||
|
|
||||||
// Return the settings if user clicked Print, otherwise null
|
|
||||||
return result == ContentDialogResult.Primary
|
|
||||||
? dialog.PrintSettings
|
|
||||||
: null!;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Log the exception if logging is available
|
|
||||||
Log.Error(ex, "Error showing print dialog");
|
|
||||||
return null!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,141 +1,212 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Graphics.Canvas;
|
using Microsoft.Graphics.Canvas;
|
||||||
using Microsoft.Graphics.Canvas.Printing;
|
using Microsoft.Graphics.Canvas.Printing;
|
||||||
using Windows.Data.Pdf;
|
using Windows.Data.Pdf;
|
||||||
using Windows.Graphics.Printing;
|
using Windows.Graphics.Printing;
|
||||||
using Windows.Graphics.Printing.OptionDetails;
|
using Windows.Graphics.Printing.OptionDetails;
|
||||||
using Windows.Storage.Streams;
|
using WinRT.Interop;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Printing;
|
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;
|
namespace Wino.Mail.WinUI.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Printer service that uses WinRT APIs to print PDF files.
|
/// Printer service that uses the WinRT print preview UI with a WebView2-backed PDF render callback.
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
public class PrintService : IPrintService
|
public class PrintService : IPrintService
|
||||||
{
|
{
|
||||||
|
private const float PdfRenderDpi = 300f;
|
||||||
|
private const float DefaultDpi = 96f;
|
||||||
|
|
||||||
private TaskCompletionSource<PrintingResult>? _taskCompletionSource;
|
private TaskCompletionSource<PrintingResult>? _taskCompletionSource;
|
||||||
private CanvasPrintDocument? printDocument;
|
private CanvasPrintDocument? _printDocument;
|
||||||
private PrintTask? printTask;
|
private PrintTask? _printTask;
|
||||||
private PdfDocument? pdfDocument;
|
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 readonly List<CanvasBitmap> _bitmaps = new();
|
||||||
private Vector2 largestBitmap;
|
private readonly List<int> _pageIndexesToPrint = new();
|
||||||
private Vector2 pageSize;
|
private Vector2 _pageSize;
|
||||||
private Vector2 imagePadding = new Vector2(64, 64);
|
private Windows.Foundation.Rect _imageableRect;
|
||||||
private Vector2 cellSize;
|
private int _pagesPerSheet = 1;
|
||||||
|
private int _columns = 1;
|
||||||
|
private int _rows = 1;
|
||||||
|
private int _sheetCount;
|
||||||
|
|
||||||
private int bitmapCount;
|
public async Task<PrintingResult> PrintAsync(nint windowHandle, string printTitle, Func<WebView2PrintSettingsModel, Task<Stream>> renderPdfStreamAsync)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
if (windowHandle == IntPtr.Zero)
|
||||||
|
return PrintingResult.Failed;
|
||||||
|
|
||||||
if (_taskCompletionSource != null)
|
if (_taskCompletionSource != null)
|
||||||
{
|
{
|
||||||
_taskCompletionSource.TrySetResult(PrintingResult.Abandoned);
|
_taskCompletionSource.TrySetResult(PrintingResult.Abandoned);
|
||||||
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
|
CleanupPrintSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the PDF file
|
_taskCompletionSource = new TaskCompletionSource<PrintingResult>();
|
||||||
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfFilePath);
|
_renderPdfStreamAsync = renderPdfStreamAsync ?? throw new ArgumentNullException(nameof(renderPdfStreamAsync));
|
||||||
pdfDocument = await PdfDocument.LoadFromFileAsync(file);
|
_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);
|
_printManager = PrintManagerInterop.GetForWindow(windowHandle);
|
||||||
|
_printManager.PrintTaskRequested += OnPrintTaskRequested;
|
||||||
printDocument = new CanvasPrintDocument();
|
|
||||||
printDocument.PrintTaskOptionsChanged += OnDocumentTaskOptionsChanged;
|
|
||||||
printDocument.Preview += OnDocumentPreview;
|
|
||||||
printDocument.Print += OnDocumentPrint;
|
|
||||||
|
|
||||||
var printManager = PrintManager.GetForCurrentView();
|
|
||||||
printManager.PrintTaskRequested += PrintingExample_PrintTaskRequested;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PrintManager.ShowPrintUIAsync();
|
await ReloadPdfDocumentAsync(_currentRenderSettings);
|
||||||
|
await PrintManagerInterop.ShowPrintUIForWindowAsync(windowHandle);
|
||||||
var result = await _taskCompletionSource.Task;
|
return await _taskCompletionSource.Task;
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Dispose everything.
|
CleanupPrintSession();
|
||||||
UnregisterPrintManager(printManager);
|
|
||||||
ClearBitmaps();
|
|
||||||
UnregisterTask();
|
|
||||||
DisposePDFDocument();
|
|
||||||
|
|
||||||
_taskCompletionSource = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (printTask != null)
|
||||||
{
|
{
|
||||||
printTask.Completed -= TaskCompleted;
|
try
|
||||||
printTask = null;
|
{
|
||||||
|
printTask.Completed -= OnPrintTaskCompleted;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void UnregisterPrintManager(PrintManager manager)
|
var printDocument = _printDocument;
|
||||||
{
|
_printDocument = null;
|
||||||
manager.PrintTaskRequested -= PrintingExample_PrintTaskRequested;
|
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()
|
private void ClearBitmaps()
|
||||||
{
|
{
|
||||||
foreach (var bitmap in bitmaps)
|
foreach (var bitmap in _bitmaps)
|
||||||
{
|
{
|
||||||
bitmap.Dispose();
|
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(_printTitle, createPrintTaskArgs =>
|
||||||
|
|
||||||
printTask = args.Request.CreatePrintTask(_currentPrintInformation.PDFTitle, (createPrintTaskArgs) =>
|
|
||||||
{
|
{
|
||||||
if (printDocument == null)
|
if (_printDocument == null)
|
||||||
return;
|
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)
|
private void OnPrintTaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args)
|
||||||
=> _taskCompletionSource?.TrySetResult((PrintingResult)args.Completion);
|
=> _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)
|
private async void OnDocumentTaskOptionsChanged(CanvasPrintDocument sender, CanvasPrintTaskOptionsChangedEventArgs args)
|
||||||
{
|
{
|
||||||
@@ -143,47 +214,19 @@ public class PrintService : IPrintService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadPDFPageBitmapsAsync(sender);
|
var newSettings = CreateRenderSettings(args.PrintTaskOptions);
|
||||||
|
|
||||||
var pageDesc = args.PrintTaskOptions.GetPageDescription(1);
|
if (ShouldReloadPdf(newSettings))
|
||||||
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.
|
await ReloadPdfDocumentAsync(newSettings);
|
||||||
return;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentRenderSettings = newSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageSize = newPageSize;
|
UpdatePreviewLayout(args.PrintTaskOptions);
|
||||||
sender.InvalidatePreview();
|
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
|
finally
|
||||||
{
|
{
|
||||||
@@ -191,85 +234,380 @@ public class PrintService : IPrintService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ReloadPdfDocumentAsync(WebView2PrintSettingsModel settings)
|
||||||
private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender)
|
|
||||||
{
|
{
|
||||||
if (pdfDocument == null)
|
if (_renderPdfStreamAsync == null)
|
||||||
return;
|
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();
|
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);
|
using var page = _pdfDocument.GetPage((uint)i);
|
||||||
var stream = new InMemoryRandomAccessStream();
|
using var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||||
await page.RenderToStreamAsync(stream);
|
var renderOptions = CreateRenderOptions(page);
|
||||||
var bitmap = await CanvasBitmap.LoadAsync(sender, stream);
|
await page.RenderToStreamAsync(stream, renderOptions);
|
||||||
bitmaps.Add(bitmap);
|
stream.Seek(0);
|
||||||
}
|
var bitmap = await CanvasBitmap.LoadAsync(_printDocument, 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 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)
|
private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args)
|
||||||
{
|
=> DrawSheet(args.DrawingSession, args.PageNumber);
|
||||||
var ds = args.DrawingSession;
|
|
||||||
var pageNumber = args.PageNumber;
|
|
||||||
|
|
||||||
DrawPdfPage(sender, ds, pageNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args)
|
private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args)
|
||||||
{
|
{
|
||||||
var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions);
|
if (_pdfDocument == null || _sheetCount == 0)
|
||||||
|
|
||||||
if (pdfDocument == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int pageCountToPrint = (int)pdfDocument.PageCount;
|
for (uint i = 1; i <= _sheetCount; i++)
|
||||||
|
|
||||||
for (uint i = 1; i <= pageCountToPrint; ++i)
|
|
||||||
{
|
{
|
||||||
using var ds = args.CreateDrawingSession();
|
using var drawingSession = args.CreateDrawingSession();
|
||||||
var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect;
|
DrawSheet(drawingSession, i);
|
||||||
|
|
||||||
DrawPdfPage(sender, ds, 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 printableSize = new Vector2((float)_imageableRect.Width, (float)_imageableRect.Height);
|
||||||
var cellDown = new Vector2(0, cellSize.Y);
|
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;
|
for (var row = 0; row < _rows; row++)
|
||||||
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)
|
for (var column = 0; column < _columns; column++)
|
||||||
{
|
{
|
||||||
var cellTopLeft = topLeft + cellAcross * column + cellDown * row;
|
if (pageIndex >= _pageIndexesToPrint.Count)
|
||||||
var bitmapInfo = bitmaps[bitmapIndex % bitmaps.Count];
|
return;
|
||||||
var bitmapPos = cellTopLeft + (cellSize - bitmapInfo.Size.ToVector2()) / 2;
|
|
||||||
|
|
||||||
ds.DrawImage(bitmapInfo, bitmapPos);
|
var bitmap = _bitmaps[_pageIndexesToPrint[pageIndex]];
|
||||||
|
var cellTopLeft = topLeft + new Vector2(cellWidth * column, cellHeight * row);
|
||||||
bitmapIndex++;
|
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)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -58,7 +59,7 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
|
|
||||||
WebViewExtensions.EnsureWebView2Environment();
|
WebViewExtensions.EnsureWebView2Environment();
|
||||||
|
|
||||||
ViewModel.DirectPrintFuncAsync = DirectPrintAsync;
|
ViewModel.RenderPdfStreamFuncAsync = RenderPdfStreamAsync;
|
||||||
|
|
||||||
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
|
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
|
||||||
{
|
{
|
||||||
@@ -92,25 +93,14 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
RendererCommandBar.InvalidateCommands();
|
RendererCommandBar.InvalidateCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PrintingResult> DirectPrintAsync(WebView2PrintSettingsModel settings)
|
private async Task<Stream> RenderPdfStreamAsync(WebView2PrintSettingsModel settings)
|
||||||
{
|
{
|
||||||
if (Chromium.CoreWebView2 == null) return PrintingResult.Failed;
|
if (Chromium.CoreWebView2 == null)
|
||||||
|
throw new InvalidOperationException("WebView2 is not initialized for printing.");
|
||||||
|
|
||||||
try
|
var nativeSettings = settings.ToCoreWebView2PdfRenderSettings(Chromium.CoreWebView2.Environment);
|
||||||
{
|
var pdfStream = await Chromium.CoreWebView2.PrintToPdfStreamAsync(nativeSettings);
|
||||||
var nativeSettings = settings.ToCoreWebView2PrintSettings(Chromium.CoreWebView2.Environment);
|
return pdfStream.AsStreamForRead();
|
||||||
var res = await Chromium.CoreWebView2.PrintAsync(nativeSettings);
|
|
||||||
|
|
||||||
return res switch
|
|
||||||
{
|
|
||||||
CoreWebView2PrintStatus.Succeeded => PrintingResult.Submitted,
|
|
||||||
_ => PrintingResult.Failed,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return PrintingResult.Failed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void OnEditorThemeChanged()
|
public override async void OnEditorThemeChanged()
|
||||||
@@ -182,7 +172,7 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
// Make sure the WebView2 is disposed properly.
|
// Make sure the WebView2 is disposed properly.
|
||||||
|
|
||||||
ViewModel.SaveHTMLasPDFFunc = null;
|
ViewModel.SaveHTMLasPDFFunc = null;
|
||||||
ViewModel.DirectPrintFuncAsync = null;
|
ViewModel.RenderPdfStreamFuncAsync = null;
|
||||||
ViewModel.RenderHtmlAsyncFunc = null;
|
ViewModel.RenderHtmlAsyncFunc = null;
|
||||||
ViewModel.ClearRenderedHtmlAsyncFunc = null;
|
ViewModel.ClearRenderedHtmlAsyncFunc = null;
|
||||||
_currentRenderedHtml = string.Empty;
|
_currentRenderedHtml = string.Empty;
|
||||||
|
|||||||
Reference in New Issue
Block a user