wpf icon indicating copy to clipboard operation
wpf copied to clipboard

[API Proposal]: Print without `PrintQueue` abstraction using the modern print dialog

Open ShortDevelopment opened this issue 1 year ago • 0 comments

Background and motivation

AFAIK there's currently no solution to print without a PrintQueue in case an application already has an instance of IXpsDocumentPackageTarget. This scenario occurs when implementing the WinRT PrintManager (The modern print dialog) for a WPF app. See PrintCompat.cs and WpfPrintDocumentSource.cs for context.

API Proposal

See XpsSerializationHelper.il for a minimal code flow.

New class XpsSerializationManagerInterop

using System.Windows.Xps.Serialization.RCW;
using System.Windows.Xps.Packaging;

namespace System.Windows.Xps.Serialization;
public sealed class XpsSerializationManagerInterop
{
    readonly XpsOMSerializationManager _serializer;
    readonly IXpsOMPackageWriter _writer;
    private XpsSerializationManagerInterop(...);

    public static XpsSerializationManagerInterop CreateFromXpsDocumentPackageTarget(object target)
    {
        // https://github.com/dotnet/wpf/blob/7b755d2bbcc9d77760f68b5e738b2587cabe1a37/src/Microsoft.DotNet.Wpf/src/System.Printing/CPP/src/PrintQueue.cpp#L4723-L4734
        XpsOMPackagingPolicy policy = new((IXpsDocumentPackageTarget)target);
        policy.EnsureXpsOMPackageWriter();

        // Internally expose XpsOMPackagingPolicy._currentFixedDocumentSequenceWriter
        var writer = policy.XpsOMPackageWriter;

        XpsOMSerializationManager serializer = new(policy);
        return new(serializer, )
    }

    // Proxy events from XpsOMSerializationManager
    public event XpsSerializationPrintTicketRequiredEventHandler? PrintTicketRequired;
    public event XpsSerializationProgressChangedEventHandler? ProgressChanged;

    public void Print(object serializedObject)
    {
        _serializer.SaveAsXaml(serializedObject);
        _serializer.Commit();

        // Close writer to flush to printer
        // Only close on success (no exception)
        // https://github.com/dotnet/wpf/blob/906eb2878bbe4d5f86e77e9a0d4f85815e9f5aaf/src/Microsoft.DotNet.Wpf/src/System.Printing/CPP/src/XpsCompatiblePrinter.cpp#L125
        _writer.Close();
    }
}

Internally expose IXpsOMPackageWriter from XpsOMPackagingPolicy

https://github.com/dotnet/wpf/blob/7b755d2bbcc9d77760f68b5e738b2587cabe1a37/src/Microsoft.DotNet.Wpf/src/ReachFramework/Serialization/manager/XpsOMPackagingPolicy.cs#L68-L72

namespace System.Windows.Xps.Packaging
{
    internal class XpsOMPackagingPolicy : BasePackagingPolicy
    {
        ...
+        public IXpsOMPackageWriter XpsOMPackageWriter => _currentFixedDocumentSequenceWriter;
        ...
        private IXpsOMPackageWriter _currentFixedDocumentSequenceWriter;
    }
}

API Usage

object target = ...; // IXpsDocumentPackageTarget com object
var serializer = XpsSerializationManagerInterop.CreateFromXpsDocumentPackageTarget(target);
serializer.PrintTicketRequired += ...;
serializer.Print(paginator); // Might throw

Alternative Designs

Create an alternative interop PrintQueue implementation:

object target = ...; // IXpsDocumentPackageTarget com object
var queue = PrintQueueInterop.CreateFromXpsOMPackageWriter(target);
var writer = PrintQueue.CreateXpsDocumentWriter(queue);
writer.Write(paginator, printTicket);

This has less new public api surface (only one new method CreateFromXpsOMPackageWriter) but might require a lot more internal api changes / abstractions.

Risks

Both solutions can likely only be used for a single print, as the IXpsOMPackageWriter has to be closed to flush the buffered data to the printer.

ShortDevelopment avatar Mar 21 '24 12:03 ShortDevelopment