Open deeprobin opened 2 years ago
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.
Tagging subscribers to this area: @dotnet/area-system-runtime See info in area-owners.md if you want to be subscribed.
Author: | deeprobin |
---|---|
Assignees: | - |
Labels: | `area-System.Runtime` |
Milestone: | - |
Tagging subscribers to this area: @dotnet/area-system-drawing See info in area-owners.md if you want to be subscribed.
Author: | deeprobin |
---|---|
Assignees: | - |
Labels: | `area-System.Drawing`, `untriaged` |
Milestone: | - |
@danmoseley this might fit better in dotnet/iot
@krwq What does printing have to do with IoT?
@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.
@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 protocol[^1] 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 API[^2] (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.
[^1]: IPP 1.0
RFC 2569 - Mapping between LPD and IPP Protocols
IPP 1.1
RFC 8011 - Internet Printing Protocol/1.1: Model and Semantics
IPP 2.0, 2.1 and 2.2
[^2]: Win32 APIs
@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.
Tagging subscribers to this area: @dotnet/area-meta See info in area-owners.md if you want to be subscribed.
Author: | deeprobin |
---|---|
Assignees: | - |
Labels: | `area-Meta`, `untriaged` |
Milestone: | - |
@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.
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.
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:
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:
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?
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:
This lays down the fundamental structure of the printing model:
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)
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.
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:
@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?
Thanks. I've split the responsibilities apart into three main areas:
JobSegment
objects is the responsibility of IPrintQueue.CreateJobAsync
's segmentFactory
parameter.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.)
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.
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:
The recommended ones also seem reasonable:
I'd also add a few of the optional operations too:
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?
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 aSystem.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?
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?
2024 and still no printing in MAUI. What a joke.
@deeprobin Sorry I just saw this bug (not sure why I didn't get the notification)... I'll add my comments later today...
OK, so initial comments:
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.
- XPS Print API (depr.)
XPS Print isn't depricated @deeprobin ...
- 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.]
Still seeing it a lot in the wild....
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:
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.
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
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 repositorySystem.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
System.Printing.Ipp
(the protocol which is used by CUPS)System.Printing.Smb
(if there are no licensing problems, because SMB is proprietary)System.Printing.Builtin
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.