Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request]: Modern library for printer communication and print control #75628

Open
deeprobin opened this issue Sep 14, 2022 · 27 comments
Open
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Meta feature-request
Milestone

Comments

@deeprobin
Copy link
Contributor

I have noticed that there is still a big flaw in printing in the .NET environment.

Even in an age of digitalization, printers are used extensively. In companies to print labels, delivery bills or labels, in the private environment to print documents, tax returns and many other applications.

Unfortunately, there is still no really good print library in the .NET environment. There is System.Drawing.Printing and the C++/CLI library in the WPF repository System.Printing.
Both are strictly limited to Windows, already very legacy and very limited.

The more companies move into the cloud, the more important it is to make the whole thing platform-independent.

My suggestion is to add an API that

  • provides low level access to the printing protocols
    • IPP Naming suggestion: System.Printing.Ipp (the protocol which is used by CUPS)
    • SMB - Naming suggestion: System.Printing.Smb (if there are no licensing problems, because SMB is proprietary)
    • Native printers (Windows printing apis / installed printers) - System.Printing.Builtin
  • An abstraction over these protocols / universal api: System.Printing

As you can easily see my suggestion is to recreate System.Printing even if this API already exists in WPF (I could well imagine that WPF would then use this API in the long run).

It would make sense to distribute this API as a Nuget package for this reason instead of including it in the BCL.


The whole thing here is just a rough wish. I have heard especially in the company where I work as well as from other companies that they really want something like this and therefore a real-world case is there.

Feel free to leave your feedback. Since this would be a larger project, if there is an interest on the part of Microsoft and the community, I would deal with it in detail and create API Proposals.

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@jeffschwMSFT jeffschwMSFT added area-System.Runtime untriaged New issue has not been triaged by the area owner labels Sep 15, 2022
@ghost
Copy link

ghost commented Sep 15, 2022

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

I have noticed that there is still a big flaw in printing in the .NET environment.

Even in an age of digitalization, printers are used extensively. In companies to print labels, delivery bills or labels, in the private environment to print documents, tax returns and many other applications.

Unfortunately, there is still no really good print library in the .NET environment. There is System.Drawing.Printing and the C++/CLI library in the WPF repository System.Printing.
Both are strictly limited to Windows, already very legacy and very limited.

The more companies move into the cloud, the more important it is to make the whole thing platform-independent.

My suggestion is to add an API that

  • provides low level access to the printing protocols
    • IPP Naming suggestion: System.Printing.Ipp (the protocol which is used by CUPS)
    • SMB - Naming suggestion: System.Printing.Smb (if there are no licensing problems, because SMB is proprietary)
    • Native printers (Windows printing apis / installed printers) - System.Printing.Builtin
  • An abstraction over these protocols / universal api: System.Printing

As you can easily see my suggestion is to recreate System.Printing even if this API already exists in WPF (I could well imagine that WPF would then use this API in the long run).

It would make sense to distribute this API as a Nuget package for this reason instead of including it in the BCL.


The whole thing here is just a rough wish. I have heard especially in the company where I work as well as from other companies that they really want something like this and therefore a real-world case is there.

Feel free to leave your feedback. Since this would be a larger project, if there is an interest on the part of Microsoft and the community, I would deal with it in detail and create API Proposals.

Author: deeprobin
Assignees: -
Labels:

area-System.Runtime

Milestone: -

@ghost
Copy link

ghost commented Sep 15, 2022

Tagging subscribers to this area: @dotnet/area-system-drawing
See info in area-owners.md if you want to be subscribed.

Issue Details

I have noticed that there is still a big flaw in printing in the .NET environment.

Even in an age of digitalization, printers are used extensively. In companies to print labels, delivery bills or labels, in the private environment to print documents, tax returns and many other applications.

Unfortunately, there is still no really good print library in the .NET environment. There is System.Drawing.Printing and the C++/CLI library in the WPF repository System.Printing.
Both are strictly limited to Windows, already very legacy and very limited.

The more companies move into the cloud, the more important it is to make the whole thing platform-independent.

My suggestion is to add an API that

  • provides low level access to the printing protocols
    • IPP Naming suggestion: System.Printing.Ipp (the protocol which is used by CUPS)
    • SMB - Naming suggestion: System.Printing.Smb (if there are no licensing problems, because SMB is proprietary)
    • Native printers (Windows printing apis / installed printers) - System.Printing.Builtin
  • An abstraction over these protocols / universal api: System.Printing

As you can easily see my suggestion is to recreate System.Printing even if this API already exists in WPF (I could well imagine that WPF would then use this API in the long run).

It would make sense to distribute this API as a Nuget package for this reason instead of including it in the BCL.


The whole thing here is just a rough wish. I have heard especially in the company where I work as well as from other companies that they really want something like this and therefore a real-world case is there.

Feel free to leave your feedback. Since this would be a larger project, if there is an interest on the part of Microsoft and the community, I would deal with it in detail and create API Proposals.

Author: deeprobin
Assignees: -
Labels:

area-System.Drawing, untriaged

Milestone: -

@krwq
Copy link
Member

krwq commented Sep 17, 2022

@danmoseley this might fit better in dotnet/iot

@deeprobin
Copy link
Contributor Author

@krwq What does printing have to do with IoT?

@krwq
Copy link
Member

krwq commented Sep 19, 2022

@deeprobin dotnet/iot is about hardware and printer is a physical device like any other. Printer doesn't really fit runtime repo in my eyes since most apps won't need it - APIs sounds super useful but it's a bit of its own beast. It seems like it would need either a separate package or be added to other repo. IMO from dotnet org dotnet/iot seems might fit best.

@deeprobin
Copy link
Contributor Author

deeprobin commented Sep 19, 2022

@krwq
Thanks for your clarification.

I assumed that dotnet/iot is only for IoT specific purposes like controlling small microcontrollers with interfaces like GPIO and not controlling printers which is mostly done over the network nowadays. For example, the IPP protocol1 is an HTTP-based protocol that works over the network (and not over serial ports, for example).
As I see it, there are currently only APIs for some microcontrollers under dotnet/iot (see src/devices), so I don't see this project there.

I would also like to refer to the description in the README.md here:

.NET can be used to build applications for IoT devices and scenarios. IoT applications typically interact with sensors, displays and input devices that require the use of GPIO pins, serial ports or similar hardware.

Since, depending on the implementation, the Windows API2 (using GDI or XPS) can be used instead of, say, IPP, I thought the dotnet/runtime repository would be a consideration.
One consideration would be to make the naming similar to IoT projects in System.Device. Like for example System.Device.Printers (by the way, I don't understand why "Device" is singular here), but the dotnet/iot repository seems wrong to me.

@richlander I saw you are active in dotnet/iot repository. I'd like to hear your thoughts on this.

However, we could also consider starting a separate repository for this. A repository like dotnet/printing.

I am also willing to support the printing project, but this will be a somewhat larger project (especially in the design of the API shape - the implementation is rather secondary) and I would definitely need support.

Footnotes

  1. IPP 1.0

    IPP 1.1

    IPP 2.0, 2.1 and 2.2

  2. Win32 APIs

@krwq
Copy link
Member

krwq commented Sep 21, 2022

@deeprobin it's true majority of the devices are I2C/SPI based but there are some exceptions, i.e. still picture or RFID/NFC devices - while device itself would maybe sometimes match criteria, reading data is way out of scope and is super complex but it would make device pointless without it. Then we have some other border-line devices i.e. NMEA 0183 - the device is serial port but protocol is essentially rather advanced parser or FT4222 which is a USB device which requires external driver but exposes GPIO/I2C/SPI so we added it for easier interop between that and other devices.

As for naming it predates me but System.Device.Gpio is only used for implementing generic purpose protocols and I think singular refers to specific protocol in use at a moment. Iot.Device.Bindings has plural form though and that's where devices live.

Regardless dotnet/iot seems like better match than dotnet/runtime even if it's not perfect and can be used as a stop (Iot.Device.Bindings specifically where making justified breaking changes is ok). If it grows too large we could potentially make this a separate nuget package.

@ericstj ericstj added area-Meta and removed area-System.Drawing untriaged New issue has not been triaged by the area owner labels Sep 21, 2022
@ericstj ericstj added this to the Future milestone Sep 21, 2022
@ghost
Copy link

ghost commented Sep 21, 2022

Tagging subscribers to this area: @dotnet/area-meta
See info in area-owners.md if you want to be subscribed.

Issue Details

I have noticed that there is still a big flaw in printing in the .NET environment.

Even in an age of digitalization, printers are used extensively. In companies to print labels, delivery bills or labels, in the private environment to print documents, tax returns and many other applications.

Unfortunately, there is still no really good print library in the .NET environment. There is System.Drawing.Printing and the C++/CLI library in the WPF repository System.Printing.
Both are strictly limited to Windows, already very legacy and very limited.

The more companies move into the cloud, the more important it is to make the whole thing platform-independent.

My suggestion is to add an API that

  • provides low level access to the printing protocols
    • IPP Naming suggestion: System.Printing.Ipp (the protocol which is used by CUPS)
    • SMB - Naming suggestion: System.Printing.Smb (if there are no licensing problems, because SMB is proprietary)
    • Native printers (Windows printing apis / installed printers) - System.Printing.Builtin
  • An abstraction over these protocols / universal api: System.Printing

As you can easily see my suggestion is to recreate System.Printing even if this API already exists in WPF (I could well imagine that WPF would then use this API in the long run).

It would make sense to distribute this API as a Nuget package for this reason instead of including it in the BCL.


The whole thing here is just a rough wish. I have heard especially in the company where I work as well as from other companies that they really want something like this and therefore a real-world case is there.

Feel free to leave your feedback. Since this would be a larger project, if there is an interest on the part of Microsoft and the community, I would deal with it in detail and create API Proposals.

Author: deeprobin
Assignees: -
Labels:

area-Meta, untriaged

Milestone: -

@ericstj ericstj added api-suggestion Early API idea and discussion, it is NOT ready for implementation feature-request labels Sep 21, 2022
@ericstj
Copy link
Member

ericstj commented Sep 21, 2022

@deeprobin do you know of any community projects that aim to fill this gap? While we are .NET library experts we aren't aiming to write every possible library that applications might need.

@deeprobin
Copy link
Contributor Author

deeprobin commented Sep 21, 2022

do you know of any community projects that aim to fill this gap?

Specifically this gap as I have described not.

There is a project called SharpIpp which implements an IPP client although this implementation has probably fallen asleep a bit and only supports limited features of this protocol.

tagging the author to possibly give his mustard here: @Zelenov

While we are .NET library experts we aren't aiming to write every possible library that applications might need.

That is, of course, clear. But this is an essential API in my opinion.

Many companies (at least in Germany) that do not use SAP and have their own ERP system mostly use C#.
Printing is needed for a good supply chain like e.g. label printing.
Many currently map this via Windows and then run their WebApp via Windows on a server.

Printing is an essential function. And the .NET libraries are there for essential functions in my opinion.

Of course, a small e-commerce company probably only needs something like this in exceptional cases. But as soon as it goes in the direction of logistics, there are, for example, customs documents that are printed, etc..

I'm not talking about adding this to the runtime repository anymore. But as an external NuGet package this is certainly very used.

While we are .NET library experts we aren't aiming to write every possible library that applications might need.

In addition, there are currently already libraries in the .NET runtime that not every use case needs. Without downplaying the value of these libraries, one could for example use Tar, Brotli, Cbor, ... could also be classified as "every possible library".

The implementation is important, even if the actual demand is probably not too big.

@edwardneal
Copy link
Contributor

This'd be quite useful, and there seems to be a gap for it. .NET's printing ecosystem needs a little attention over the next few versions. Some of it is tightly coupled to the GUI, (albeit for good reason at the time) while other parts haven't kept pace with the cross-platform transition.

If we want to print a document right now, we've got a few different options:

  • Windows Forms has PrintDocument
  • UWP/WinRT has Windows.Graphics.Printing.PrintManager
  • WPF has System.Printing
  • WinUI has nothing (sort of)
  • Maui has nothing
  • Blazor has a third-party component
    Stepping from the simplest view of a UI-based print application, backend services like Windows services need to draw in System.Windows.Forms. Sending a document to a cloud print queue (such as Azure Universal Print) isn't possible using the normal interfaces, and if we want to print from Linux applications of any kind then we need to use a third-party library like SharpIpp.

There are also other tasks which we'd reasonably view as printing, but which don't necessarily fit into the current object model. This can include things like barcode/label printing, and 3D printing.

If .NET had a clear printing-related object model, we'd be able to provide a common interface for all of the above, and eventually expand it with cross-platform implementations; the current options could then be deprecated. I think there might be a case for .NET to have two implementations (for Windows and Linux/MacOS support) at some point, but just having that common interface would be a useful starting point. I'd eventually hope to see libraries for third-part printer types (whether that's a cloud printing service, a Klipper/Moonraker 3D print API, or something else.)

This model would only cover submitting, managing and streaming data to print jobs. The hardware-specific tasks (paper tray status, printer bed temperature, ink levels) could easily go in dotnet/iot, as suggested - it's too tied to the physical hardware, when the Windows spooler and CUPS both work with print queues.

With this type of approach, a user would be able to print a document in one or two ways:

  • Generate an EMF context using System.Drawing.Common and submit it to a Winspool "print queue provider", streaming it page by page
  • Read a PDF document from disk and submit it to an IPP Anywhere print queue provider
  • Use a custom provider to generate ZPL commands and submit it to either of these print queue providers, sending it to a Zebra label printer
    Data would effectively flow from a typed object, through a translation layer, into the print queue provider. The print queue provider manages the underlying queue.

I think this'd mean that less work is taken on than we'd expect: a common design for the object model, one (perhaps two) translation layers (the first being a simple "pass through from Stream", the second possibly allowing the rendering of a Graphics object to an EMF HDC) and either one or two print queue providers for the Windows spooler or CUPS.

Could we consider starting this work for .NET 9/10?

@edwardneal
Copy link
Contributor

I've thought about an API design, hopefully to start a discussion of the amount of work required. I'd be happy to help with that work, but it's foundational enough that I think it should definitely be part of a .NET package rather than a community library. Windows Forms, WPF and WinRT have the functionality built in, so there's some precedent there (and being able to unlock printing on Linux is nice too.)

I'd suggest three layers:

  1. Base object model, which models a generic set of print queues, jobs and job segments
  2. "Guardrail object model" which inherits from 1. and gives developers a point to inherit from if they want to print a paginated document or similar
  3. Implementation - largely platform-specific, but following the base object model

Base object model

This lays down the fundamental structure of the printing model:

  1. A platform and/or service has one or more PrintQueueProviders. These might be Winspool on Windows, CUPS on Linux, a 3D printer, etc. The PQP has the responsibility to handle transport to the service, authentication and enumeration of print queues.
  2. A PrintQueueProvider has multiple PrintQueues. Each print queue just maps to a single native queue. It's addressable by name, we don't put any further constraint on it. It doesn't refer to hardware at all - if we want, that's handled by something in dotnet/iot. A PrintQueue may print one kind of PrintJob.
  3. A PrintJob is exactly as expected. It's got a unique identifier, name, status, etc., and it can be paused, resumed, cancelled. It might also have other properties depending upon its derived types. Most importantly, it also has one or more linked lists of JobSegments. Every linked list refers to one kind of JobSegment. I've added this simply to leave the door open for more complex operations, most of the time there'll be exactly one linked list of JobSegments.
  4. A JobSegment is usually a page in the document. It has a sequence number, it has a byte array associated with it, it might have other properties (in the case of pages, this could be orientation, margins, etc.)

I'd only expect these to have basic routing in them, providing an interface for the creation of jobs, and the streaming of job segments (probably from an IAsyncEnumerable)

Initial API shape
public abstract class PrintQueueProvider
{
    // Gets all queues capable of handling a specific type of print job.
    public abstract IAsyncEnumerable<IPrintQueue<TPrintJob>> EnumerateQueuesAsync<TPrintJob>()
        where TPrintJob : PrintJob<TPrintJob>;
    // Gets a queue by its name and print job type. A print queue might support multiple
    // job types (e.g. sending raw printer command jobs, and printing a raster image.)
    // In such a case, the queue class would implement IPrintQueue of each job type.
    public abstract ValueTask<IPrintQueue<TPrintJob>> GetQueueAsync<TPrintJob>(string name)
        where TPrintJob : PrintJob<TPrintJob>;
}

public interface IPrintQueue<TPrintJob>
    where TPrintJob : PrintJob<TPrintJob>
{
    // Creates a single job of type TPrintJob, using segmentFactory to convert from a managed
    // object into a list of job segments. This might cover a single segment of raw printer commands, or
    // it might accept a paginated document's list of pages.
    ValueTask<TPrintJob> CreateJobAsync<T, TJobSegment>(T sourceObject,
        Func<T, IAsyncEnumerable<TJobSegment>> segmentFactory)
        where TJobSegment : JobSegment<TJobSegment>;

    // Enumerates all jobs in the queue.
    IAsyncEnumerable<TPrintJob> EnumerateJobs();
}

public abstract class PrintJob<TSelf>
    where TSelf : PrintJob<TSelf>
{
    // <common properties - DateQueued, DateStarted, TotalSegments, SegmentsPrinted, SegmentsGenerated>
    public JobStatus Status { get; protected set; }
    public string Title { get; private set; }
    public int Identifier { get; protected set; }

    // Gets the first segment in the linked list - page one, or the initial setup commands for a 3D printer.
    public abstract ValueTask<TJobSegment> GetFirstSegmentAsync()
        where TJobSegment : JobSegment<TJobSegment>;

    // Indicates whether this print job will ever allow a particular job segment type.
    public abstract bool SupportsSegmentType<TJobSegment>()
        where TJobSegment : JobSegment<TJobSegment>;

    // Command and wait methods
    public abstract ValueTask PauseAsync();
    public abstract ValueTask ResumeAsync();
    public abstract ValueTask CancelAsync();
    public abstract ValueTask WaitForStatusAsync(JobStatus status);
    public abstract ValueTask WaitForSegmentAsync(int segmentNumber);
}

public abstract class JobSegment<TSelf>
    where TSelf : JobSegment<TSelf>
{
    public int SequenceNumber { get; private set; }

    // Gets the data to be sent to the printer in a printer-recognisable format (e.g. EMF, PostScript, GCode)
    public abstract ValueTask<ReadOnlyMemory<byte>> GetSegmentDataAsync();

    // Gets the next segment. Returns null if this is the last segment.
    public async ValueTask<TSelf?> GetNextSegmentAsync();
}

"Friendly" object model

This abstracts the raw model which treats print job page data as a binary blob, and exposes it as an object model which a developer could reasonably work with - and more importantly, it means that all downstream clients have a consistent view of obvious abstractions, such as a paginated print job.

Initial API shape
public abstract class PaginatedRasterPrintJob : PaginatedPrintJob
{
}

public abstract class PaginatedPrintJob : PrintJob<PaginatedPrintJob>
{
    public override bool SupportsSegmentType<TJobSegment>()
        => typeof(Page).IsAssignableFrom(typeof(TJobSegment));
}

public abstract class Page : JobSegment<Page>
{
}

Implementation

Most of the above work is design-based, with some code to plumb data through to individual job segments. The core implementation work here would be to fold Windows' print spooler into this design, then to add an IPP Anywhere implementation to enable CUPS on Linux and Mac. I'm happy to help with that.

I'd also add the two higher-level utility classes I mentioned a while ago: one which reads from a Stream and outputs a single JobSegment, and another which accepts a Graphics object and outputs it to a printer as a raster image. These enable three scenarios:

  1. Direct signalling of a more complex printer (such as a Zebra printer, which uses a different control language.)
  2. Sending PDF and OpenXPS documents to an IPP printer capable of processing them, cutting out all GUI components which are currently in the middle
  3. Clears the way for Windows Forms to use it (and eventually, WPF/WinRT/WinUI/Maui)

@deeprobin
Copy link
Contributor Author

@edwardneal

Thank you for your initiative and the first draft proposal for the API shape.
I really like it in principle.

I'd still like a few refinements such as cancellation tokens for the asynchronous methods, but otherwise I think it's a good place to start.

However, I'm still a little undecided myself.
Functions such as pause, resume and cancel are of course important functions in the area of printing.

The Windows Print Queue & (probably) CUPS also support this in any case.
IPP also supports these functions - but again, I'm not sure if it's guaranteed.

However, if we manually communicate "RAW" with our printer via TCP, I have no idea how we can pause or even cancel the print - these are perhaps special functions in e.g. ZPL or PCL, but nothing related to the print transport.

Maybe it would make sense to introduce a property like "IsPauseSupported" and throw a NotSupportedException in the method if it is not supported.

Another idea would be to build individual interfaces for the individual functions, similar to BCL with Generic Math.

@krwq What do you think of this proposal?

@edwardneal
Copy link
Contributor

Thanks. I've split the responsibilities apart into three main areas:

  • Communication with the spooler/queue provider (whether contacted via PInvoke or HttpClient) is handled by the PrintQueueProvider instance. This is essentially the transport and authentication layer.
  • Conversion of a managed .NET object (such as a Bitmap, or a pass-through Stream) to a stream of JobSegment objects is the responsibility of IPrintQueue.CreateJobAsync's segmentFactory parameter.
  • Within each JobSegment, GetSegmentDataAsync is responsible for generating data to be sent to the spooler (whether that's a PDF or OpenXPS document sent to IPP, PostScript commands sent to the Windows spooler, etc.)

I'd personally expect the cancellation of the print job to signal the print spooler, and the spooler would handle the underlying printer comms. Azure Universal Print is a useful example here: the client will probably never be able to contact the underlying printer, but that doesn't matter because it's sending the command to the AUP service. In some restricted enterprise networks, the "local queue" presented by the Windows print spooler might actually sit on a remote print server, and the client is firewalled away from the printer itself.

This should hopefully simplify things a little, because it means that we don't need to worry about developing for an unbounded set of printer protocols - we could just call SetJob or invoke a Cancel-Job operation and run.

CancellationTokens make a lot of sense. It could be unexpected if we're making REST calls and we invoke the CancellationToken after the IPP server has performed the action but before it's returned a response to the client. I think that's just a matter of documentation though - practically anything could fall into that category, it's just more obvious when there's something coming out the printer!

We might need quite a few properties like "IsPauseSupported". I'm not sure whether we'd want to have that, a Stream-style CanPause property on the PrintJob, a CapabilitySupported(PrintCapability.Pause) approach or an IPrintQueueProviderWhichSupportsPausingJobs interface. It probably needs to support situations where the PrintQueueProvider supports pausing print jobs, but one specific print job has transitioned to a state which is unpausable (e.g., it's being cancelled, or it's in the final stages of completing.)

@deeprobin
Copy link
Contributor Author

I'd personally expect the cancellation of the print job to signal the print spooler, and the spooler would handle the underlying printer comms. Azure Universal Print is a useful example here: the client will probably never be able to contact the underlying printer, but that doesn't matter because it's sending the command to the AUP service. In some restricted enterprise networks, the "local queue" presented by the Windows print spooler might actually sit on a remote print server, and the client is firewalled away from the printer itself.

Perhaps we could include the product owners of Azure Universal Print in this topic.

They already have this Universal Print product, which could benefit from this API.
As far as I know, Universal Print is currently implemented in .NET.

CancellationTokens make a lot of sense. It could be unexpected if we're making REST calls and we invoke the CancellationToken after the IPP server has performed the action but before it's returned a response to the client. I think that's just a matter of documentation though - practically anything could fall into that category, it's just more obvious when there's something coming out the printer!

I think a cancellation token must be present on the asynchronous methods in any case.

In this case, a cancellation would only affect the current operation and not the entire print job.
As far as I know, this is also the usual scheme.

If a REST call is cancelled (e.g. socket close), I would expect the remote not to execute it either.

But as you say, this would have to be documented.


What I have now noticed afterwards:

In your proposal, you also define a "Status" property at PrintJob level.

Status is a frequently changing property. Especially where the print queues are "externally managed" (e.g. on the printer or print server), you would have to execute a request every time to keep them up to date.

Wouldn't an asynchronous method with a ValueTask return make sense here?

Something like:

public abstract partial class PrintJob<TSelf>
    where TSelf : PrintJob<TSelf>
{
    public ValueTask<JobStatus> GetJobStatusAsync(CancellationToken cancellationToken = default);
}

I think we should generally try to stick to the Print Working Group (PWG) in this area, as these printing topics are already dealt with very thoroughly there.

There is generally a semantic model there, which could perhaps help us in future proposals.

@edwardneal
Copy link
Contributor

Making the Status property an async function sounds sensible to me. The Windows spooler is synchronous, but on Linux (and for anything remote) that probably won't hold true.

The PWG have some useful publications. Pages 35-37 of the IPP Shared Infrastructure Extensions list common operations, which would help inform the appropriate interface on PrintJob and PrintQueue. A lot of their data model also makes sense, and at a glance, there's a lot of overlap with the Windows print spooler - there are only so many ways to reinvent that wheel!

The mandated operations for an IPP client look pretty sensible:

  • Cancel-Job / Cancel-My-Jobs
  • Close-Job
  • Create-Job
  • Get-Job-Attributes
  • Get-Printer-Attributes
  • Send-Document

The recommended ones also seem reasonable:

  • Cancel-Subscription / Create-Job-Subscriptions / Create-Printer-Subscriptions / Get-Notifications / Renew-Subscription
  • Identify-Printer
  • Validate-Document / Validate-Job

I'd also add a few of the optional operations too:

  • Get-Jobs / Set-Job-Attributes
  • Get-Printer-Supported-Values
  • Get-Subscriptions / Get-Subscription-Attributes
  • Hold-Job / Resume-Job / Resubmit-Job

I think there's value in looking for overlap between the widest variety of printers: the PWG standards, Winspool's prior art, one of the more unusual printers (I've referred to Zebra printers, but the goal's just to find a printer which isn't a standard inkjet / laser printer) and a 3D printer. Hopefully if there's anything key which overlaps between them, it can go in the root PrintJob class.

Speaking more widely to anyone looking at this from the .NET teams: which way around should this go? Do we need a firm API proposal to request a System.Printing library, or should we start with agreement in principle to provide a System.Printing library and then start fleshing out the API proposals?

@deeprobin
Copy link
Contributor Author

Making the Status property an async function sounds sensible to me. The Windows spooler is synchronous, but on Linux (and for anything remote) that probably won't hold true.

Yes, CUPS seems to be very "async" - many functions have callbacks.
But android.print seems very synchronous (but I think this is still common in the Java environment - no CompletableFutures, ...)

Is perhaps the person responsible for the WinNT Print Spooler also here somewhere on GitHub? (cc MSFT)
Maybe he can also give us some information in this context as far as it does not violate internal things at Microsoft.

Speaking more widely to anyone looking at this from the .NET teams: which way around should this go? Do we need a firm API proposal to request a System.Printing library, or should we start with agreement in principle to provide a System.Printing library and then start fleshing out the API proposals?

In principle, such APIs should always be proposed at some point in order to comply with the typical API review process.
However, I think it will be sufficient to deal with them in detail only when the maintainers have given the green light, as this API probably goes beyond a few methods.

@ericstj @bartonjs
Could one of you take a look at the topic and possibly address it internally before we discuss it further?

@deeprobin
Copy link
Contributor Author

Hey @michaelrsweet,

I hope you don't mind me pinging you here.

We are currently discussing what a universal printing API in the .NET environment could look like in general.
Do you have any fundamental additions to the current "draft proposal" from the PWG's point of view?

@wstaelens
Copy link

wstaelens commented May 15, 2024

2024 and still no printing in MAUI. What a joke.

#78629

@michaelrsweet
Copy link

@deeprobin Sorry I just saw this bug (not sure why I didn't get the notification)... I'll add my comments later today...

@michaelrsweet
Copy link

OK, so initial comments:

  • Base your Client API on IPP Everywhere since all of the driverless network/USB printing "standards" are either based on it or compatible with it.
  • While you will have some use cases for using printer-specific "Page Description Languages" (PDLs) like ZPL, the focus should be on higher-level formats like PDF for documents and JPEG/PNG for images.
  • The local spooler (whatever it is) should handle the actual communication with the printer since most printers can't handle spooling.
  • Some platforms have persistent print queues, others are transient lasting only as long as print jobs are active, some are a hybrid (CUPS is like this).

WRT printing not being commonly used by applications, that is IMHO a fairly myopic view of things. Printing is a key part of shipping (200,000,000,000 packages per year with shipping labels, packing lists, and hazard labels), medicine (wrist bands, drug prescriptions, test reports, "replacement parts", etc.), and commerce (invoices, receipts, etc.) In some cases the content you are printing might be provided electronically (particularly packing lists, test reports, invoices, and receipts) but you still need to produce the content even if you don't print it.

So I think a key part of any printing API is an API for producing (paginated) documents that can be printed, emailed, saved, etc.

@wstaelens
Copy link

  • XPS Print API (depr.)

XPS Print isn't depricated @deeprobin ...

@deeprobin
Copy link
Contributor Author

deeprobin commented May 22, 2024

  • XPS Print API (depr.)

XPS Print isn't depricated @deeprobin ...

@wstaelens
Are you sure?
Maybe "de facto deprecated".

The Win32 docs don't recommend them very good.
See https://learn.microsoft.com/en-us/windows/win32/printdocs/xps-printing

[The XPS Print API is not supported and may be altered or unavailable in the future. Client applications should use the Print Document Package API instead.]

@wstaelens
Copy link

Still seeing it a lot in the wild....

@edwardneal
Copy link
Contributor

Thanks @michaelrsweet, modelling a client API round IPP Everywhere makes perfect sense. Your point around temporary print queues is also helpful: it seems to me that if .NET gains printing support in the future, it'd be best to think in terms of "print destinations" or similar, so as to specifically avoid a situation where .NET refers to something as a "print queue", while Windows might send the data to a Print Document Package-style printing app, CUPS might send it to a Printer Application's IPP endpoint, etc.

Any future object model should probably separate concerns similarly to CUPS and IPP. I think the Windows printing API (whether that's Winspool or a Print Document Package API) can fit fairly tidily within this:

  • Discovery (which might be a set of API calls to the Windows spooler API or to CUPS, direct multicast discovery, web API calls to Azure Universal Print, etc.)
    • This would need to include some capability discovery for things like media types, etc.
  • Transport. In some cases, the "transport" might just be calls to the Windows spooler/CUPS API. In other cases, I imagine someone might want to write their own.
  • Job management. This would always need to be handled by the OS' spooler.
  • Document and page management. The Windows spooler runs page-by-page and assumes exactly one document per job. The CUPS/IPP object model seems to expect at least one document per job.
  • Streamed PDL / binary blobs within a document.
    • There's some overlap here with the discovery process, as the client selects the PDL it prefers.

Some implementations of the discovery and the transport layers would be a better fit for the dotnet/iot repo, or for user libraries. I'd expect the generation of the PDL / paginated documents to also be handled completely by user libraries. However, for printing support to be useful in things like Windows services, WPF, ASP.NET Core and Windows Forms, a core object model needs to be in the basic runtime, as does an implementation of Winspool / CUPS which is exposed via that model.

This would go a long way to undoing the fragmentation which is currently in place across WinForms, UWP and WPF, and also provides a starting point for Maui (and perhaps ASP.NET Core.) A community library can't cover these platforms: even if it takes a long time, the end goal would hopefully be for the OOTB common controls for each UI framework to wrap around the same common object model (ideally, with community libraries building pluggable platform-specific discovery, data formatting or transport components.)

Coming back to the wider point about printing in general: it's probably not a headline feature of a major .NET release, but it remains important. Physical paper documents are sometimes required for legal/contractual reasons; paginated reports are generated by software like SQL Server Reporting Services; practically any warehouse will print finished goods labels. At the moment, .NET doesn't present a clear, cross-platform way for developers to engage with printing (whether that's a traditional printer in an office, something in an industrial setting which requires lower-level control over the same operations like a label printer, or something more unusual such as a 3D printer.)

If .NET 10 has an object model and 2-3 basic implementations which expose Winspool, CUPS and potentially the Print Document Package API, it starts to unblock consistent cross-platform functionality in most of the downstream implementations.

@GuildOfCalamity
Copy link

Printing in WinUI3 is still broken and it doesn't seem like anyone is trying to fix it. Many of us still need the ability to print to PDF, print to file, etc. similar issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Meta feature-request
Projects
None yet
Development

No branches or pull requests

9 participants