xpra icon
Bug tracker and wiki

#913 closed enhancement (fixed)

html5 printing

Reported by: Josh Owned by: Josh
Priority: major Milestone: 0.16
Component: html5 Version: trunk
Keywords: Cc: Antoine Martin

Description

Looking at #598 it seems as though we can register a printer to be created on the Xpra server and get a PDF output from it.

I think this could be integrated into the HTML5 client by creating a "browser printer" and loading the PDF into a new window / tab that the user then initiates printing again through the local browser (possibly using https://mozilla.github.io/pdf.js/ )

Attachments (1)

sample-print-file.ps (176.6 KB) - added by Antoine Martin 21 months ago.
example of a file we generate when printing

Download all attachments as: .zip

Change History (26)

comment:1 Changed 22 months ago by Antoine Martin

Yes, the printer definition we send to the server is minimal at present, so we don't need to worry about knowing anything about the local printers (paper size, etc). So all you would need to send is the printer name.
The more difficult part is going to be feeding the PDF data packet to the page, but I suspect pdf.js will support that.

comment:2 Changed 22 months ago by Josh

Yes, it should be able to be rendered by pdf.js, or in Webkit browsers natively through a base64 encoded data uri.

Seems that most detail needed for implementation starts around here https://www.xpra.org/trac/browser/xpra/trunk/src/xpra/client/client_base.py#L723 would just need to make a fake "html5 printer" dict to send to server in the right format, something like

{
  "HTML5 client": {
    "printer-info" : "Print to PDF in client browser",
    "printer-make-and-model": "HTML5 client version"
  }
}

comment:3 Changed 21 months ago by Josh

@antoine: the mimetype of the file data sent when printing is always application/postscript... is the data really postscript or PDF?

comment:4 Changed 21 months ago by Antoine Martin

Yes, it is hard-coded in the "xpraforwarder" cups backend. IIRC I had tried to use pdf and had some problems on one of the platforms I think (win32?).

I'll try to see if I can move this to the client-side platform code only and send the "real" mimetype instead. (supporting 0.15 will make this interesting, or maybe I won't bother)

Changed 21 months ago by Antoine Martin

Attachment: sample-print-file.ps added

example of a file we generate when printing

comment:5 Changed 21 months ago by Antoine Martin

OK, AFAIK we can't easily inspect the data which is fed to the cups pdf ppd filter, or the file that is fed to the xpraforwarder (potential enhancement there), but we can see the resulting file client side (which should be identical to the one the xpraforwarder sees) using:

XPRA_DELETE_PRINTER_FILE=0 xpra attach -d printing ...

This should show you what filename was used, and you can then inspect it.

The file attached to this ticket is a "hello world" example printed from gedit.
The header looks like this:

%!PS-Adobe-3.0
%%HiResBoundingBox: 0 0 596.00 842.00
%%Creator: GPL Ghostscript 916 (ps2write)
%%LanguageLevel: 2
%%CreationDate: D:20150809212358+07'00'
%%For: (antoine)
%%Title: (Unsaved Document 1)
%RBINumCopies: 1
%%Pages: (atend)
%%BoundingBox: (atend)
%%EndComments
%%BeginProlog
...

So... we are sending postscript and not PDF.
No idea why the output of the cups-pdf filter is not actually PDF! (I guess that explains why I had to force the mimetype to postscript)

I have even tested the backend using raw mode (important fix required for raw mode: r10248):

XPRA_PPD_FILE="" xpra start ...

And gedit was actually sending PDF to the print queue! So PDF + PDF filter -> Postscript!?

Options I can think of:

  • use a JS library that handles postscript
  • enhance the printing code to receive a list of mimetypes from the client and convert to one of those (ie: using pstopdf, or eps, svg or whatever) - means spawning a new process, best if we do this from the short-lived cups backend than in the server code.

@joshiggins: what is your preference here?

Notes:

comment:6 Changed 21 months ago by Josh

I am by no means an expert in CUPS but...

There is some nice info in here Bug #820820 “cups-pdf should not convert PDF print jobs to PS th...”

It seems as though many applications are already sending PDF or at least, will get converted by *topdf filters in modern CUPS PDF as Standard Print Job Format which probably explains why gedit is actually resulting in PDF to the queue (in which case, the least cost filter would be pdftopdf).

This makes me think that CUPS-PDF is not needed at all... by increasing the cost factor of pstops filter will result in a PS -> PDF conversion, which is apparently default in newer distros shipping CUPS >=1.6.

We could support most configurations by simply checking the mimetype against a list supported by the client as you suggest, and running ps2pdf to convert if necessary. But in reality, what I understand is that we should mostly be getting PDF nowadays anyway.

I have no idea why PDF + PDF filter -> Postscript (is CUPS-PDF being invoked at all?) but in any case we shouldn't rely on this quirk - AFAICT the current printing method uses just postscript so we shouldn't even need it.


To summarise (sorry!)

  • Adding printer without PPD, in modern distro we should expect PDF input to the backend script (built in *topdf filters)? - Need to confirm/test this
  • Best to check mimetype(s) that the client supports and whether this is what we get in the backend script
  • For server running with older CUPS we don't support PDF (thus printing in HTML5 client also) and dump CUPS-PDF

comment:7 Changed 21 months ago by Antoine Martin

Adding printer without PPD, in modern distro we should expect PDF input to the backend script (built in *topdf filters)? - Need to confirm/test this


Yes, I have tested this using the XPRA_PPD_FILE="", but this breaks printing text files via "lpr" - more on this below.


Best to check mimetype(s) that the client supports and whether this is what we get in the backend script


I agree... We'll need a simple map: {"application/ps" : "pstopdf", ...}


For server running with older CUPS we don't support PDF (thus printing in HTML5 client also) and dump CUPS-PDF


I don't understand this. Can you elaborate?


Thanks for the ticket link.
It would be nice if they could fix the filter... but there have been no updates in this ticket for almost 2 years. So let's forget about this for now.

Another option I can think of would be to run without filter (XPRA_PPDF_FILE="") and emulate what cups does: run "texttopdf" or whatever is needed on the input (and skip all filters for pdf). Then we also avoid the roundtrip via ps. Not sure if we lose any options like page setup (A4 vs A3 and such).

comment:8 Changed 21 months ago by Josh

Can you elaborate?


I was thinking that we could use a PDF printing workflow if available, and degrade gracefully to PS workflow if the server had an old CUPS. Within the backend script, we would just have to match the client supported mimetype with the data we got (because it is guaranteed to be PS or PDF, unless we printed in raw mode, which we shouldn't?). We don't need CUPS-PDF because all the *topdf filters are built in (unless it is an older CUPS, in which case we fall back to PS but lose printing support in the HTML5 client). Does that make sense?


It seems that there is also a PPD file already for us that handles PDF:

joshiggins@josht440:/usr/share/cups$ lpinfo --make-and-model 'Generic PDF Printer' -m
lsb/usr/cupsfilters/Generic-PDF_Printer-PDF.ppd Generic PDF Printer

On Ubuntu, this is under /usr/share/ppd/cupsfilters/ (I found it in the same place on F22 also) and low and behold, it just works, even with text file input from lpr.

For Postscript output, old CUPS will have /usr/share/cups/model/postscript.ppd or new CUPS uses the driver drv:///sample.drv/generic.ppd.


Therefore, I would propose:

  • Client advertises supported printing mimetypes in hello.
  • If client supports PDF, we should check for existence of Generic-PDF-Printer-PDF.ppd. If not available, and client also supports PS, then create the printer with generic Postscript driver. Otherwise, we could not find mutual support and disable printing.
  • If client supports only PS, then create the printer with generic Postscript driver.
  • The xpraforwarder has to tolerate both PS and PDF input and use the correct mimetype in the xpra print.
  • If the client unexpectedly receives print data it does not support, we log it at the client and drop it.


This way we can hopefully not break PS on platforms where pdf was not working, and get PDF support without external dependencies on server with supported CUPS. I could probably have a pretty good stab at some of this if that sounds reasonable...

comment:9 Changed 21 months ago by Josh

Of course, the ugly solution is to run XPRA_PPDF_FILE="/usr/share/ppd/cupsfilters/Generic-PDF_Printer-PDF.ppd" xpra start ... and ignore the mimetype in the HTML5 client...

comment:10 Changed 21 months ago by Antoine Martin

Owner: changed from Josh to Antoine Martin
Status: newassigned

I think that's a good plan, and if you can ignore the mimetype for now as a workaround then I'll deal with the server changes later.

comment:11 Changed 20 months ago by Antoine Martin

Owner: changed from Antoine Martin to Josh
Status: assignednew

Done in r10432, see commit message for details.

The new "mimetypes" feature is per forwarded printer rather than in the hello packet, which is more flexible.
If the client supports both pdf and postscript, we choose pdf as of r10433.

@joshiggins: you should be able to simply add: mimetypes : ["application/pdf"] to your virtual printer definition to get PDF as output.

If that works for you, please re-assign to afarr for testing.

comment:12 Changed 20 months ago by Josh

@antoine: The mimetype feature appears to be working well but it doesn't look like the PPD file is actually passed to the lpadmin command, the created printer is appearing as raw.

2015-08-24 20:28:43,909 using printer definition '['-P', '/usr/share/ppd/cupsfilters/Generic-PDF_Printer-PDF.ppd']' for application/pdf
2015-08-24 20:28:43,910 pycups_printing adding printer: ['-p', 'HTML5-client', '-E', '-v', 'xpraforwarder:/tmp?mimetype=application%2Fpdf&socket-path=%2Fhome%2Fjoshiggins%2F.xpra%2Fubuntu-10&remote-printer=HTML5+client&remote-device-uri=None&socket-dir=%2Fhome%2Fjoshiggins%2F.xpra&source=ed27963d2c843e8de873ea15c08ad3b6&display=%3A10', '-D', 'Print to PDF in client browser', '-L', 'via xpra', '-o', 'printer-is-shared=false', '-u', 'allow:joshiggins']
2015-08-24 20:28:43,911 exec_lpadmin(['-p', 'HTML5-client', '-E', '-v', 'xpraforwarder:/tmp?mimetype=application%2Fpdf&socket-path=%2Fhome%2Fjoshiggins%2F.xpra%2Fubuntu-10&remote-printer=HTML5+client&remote-device-uri=None&socket-dir=%2Fhome%2Fjoshiggins%2F.xpra&source=ed27963d2c843e8de873ea15c08ad3b6&display=%3A10', '-D', 'Print to PDF in client browser', '-L', 'via xpra', '-o', 'printer-is-shared=false', '-u', 'allow:joshiggins']) command=['/usr/sbin/lpadmin', '-p', 'HTML5-client', '-E', '-v', 'xpraforwarder:/tmp?mimetype=application%2Fpdf&socket-path=%2Fhome%2Fjoshiggins%2F.xpra%2Fubuntu-10&remote-printer=HTML5+client&remote-device-uri=None&socket-dir=%2Fhome%2Fjoshiggins%2F.xpra&source=ed27963d2c843e8de873ea15c08ad3b6&display=%3A10', '-D', 'Print to PDF in client browser', '-L', 'via xpra', '-o', 'printer-is-shared=false', '-u', 'allow:joshiggins']
2015-08-24 20:28:43,932 client does not support any csc modes with vp9
2015-08-24 20:28:43,933 client does not support any csc modes with vp8
2015-08-24 20:28:43,968 returncode(['/usr/sbin/lpadmin', '-p', 'HTML5-client', '-E', '-v', 'xpraforwarder:/tmp?mimetype=application%2Fpdf&socket-path=%2Fhome%2Fjoshiggins%2F.xpra%2Fubuntu-10&remote-printer=HTML5+client&remote-device-uri=None&socket-dir=%2Fhome%2Fjoshiggins%2F.xpra&source=ed27963d2c843e8de873ea15c08ad3b6&display=%3A10', '-D', 'Print to PDF in client browser', '-L', 'via xpra', '-o', 'printer-is-shared=false', '-u', 'allow:joshiggins'])=0

comment:13 Changed 20 months ago by Josh

p.s. r10436 brings html5 printing support to working state for Chrome and Firefox by opening a new window with the PDF as a base64 encoded data URI.

This doesn't play as well with IE, but neither does pdf.js.

comment:14 Changed 20 months ago by Antoine Martin

Doh, r10437 fixes that. Not much difference in the output (I had forgotten to re-test with raw applications like 'lpr' after some last minute code style changes!).

@joshiggins: if you're happy with the state of things, please re-assign to afarr for testing.

@afarr: it is worth spending a bit of time to see the output of the printing setup with different settings, under different OSes, as you may want to use 'raw' with your app, or not.


Testing recap:

  • XPRA_PRINTER_RAW=1 must be specified on both the client and server to allow raw mode. In this case, if raw mode is selected (see below), the document given by the application will be forwarded as-is to the client. If the application sends a PDF or postscript, this should generally work - for other mimetypes... it may or may not work (probably won't work on win32 since we feed stuff to ghostscript, which expects PDF or postscript) - it is theoretically possible to enhance the win32 code to feed other mimetypes to the printing system (like "text/plain"), but I don't see the point. Some would argue that the cups backend filtering makes this safer (PDF/PS have been used as attack vectors before) but I would argue that if someone can control the PDF output, you probably have bigger problems already.
  • r10438 adds the ability for the client to specify which mimetype it prefers to receive, the only options are: "application/pdf", "application/postscript" and "raw" (assuming raw mode is enabled as per above), ie: XPRA_PRINTING_PREFERRED_MIMETYPE="application/postscript" should provide the same output as previous versions (going via Postscript instead of PDF)
  • you can inspect the output visually and waste paper each time, or you can also stop the print queue (ie: on win32, unplugging the printer is enough, the printer should remain "configured" - on Linux, turning the printer off usually removes the printer: we detect it is turned off and remove it from the list so you won't be able to print to it, "stopping" the printer from the control panel should do it) then look at the PS/PDF document we send for printing. You may want to use XPRA_DELETE_PRINTER_FILE=0 (client side) to keep the documents around after they've been sent to the print queue so you can open them with a document viewer.
  • for testing, it can be useful to add the local printers to the local server (making it loop via xpra rather than print directly, the printer will appear twice in the print dialogs) using XPRA_ADD_LOCAL_PRINTERS=1 (server side), then you can test with a single machine + printer
  • -d printing shows a lot of useful information
  • the cups backend will log to syslog, on Fedora you can see those messages with sudo journalctl -f -t xpraforwarder
Last edited 20 months ago by Antoine Martin (previous) (diff)

comment:15 Changed 20 months ago by Josh

@antoine: It works really well for PDF printing and in the HTML5 client!

But I think that the postscript side might be broken - is it safe to rely on a quirk of the CUPS-PDF PPD that is actually outputs postscript (whether expected or not)? There are some other options for postscript output...

joshiggins@ubuntu:/usr/share/ppd/cupsfilters$ lpinfo --make-and-model 'Generic Postscript Printer' -m
drv:///sample.drv/generic.ppd Generic PostScript Printer
foomatic-db-compressed-ppds:0/ppd/foomatic-ppd/Generic-PostScript_Printer-Postscript.ppd Generic PostScript Printer Foomatic/Postscript (recommended)

comment:16 Changed 20 months ago by Antoine Martin

@joshiggins: very good point, maybe we should try to find all the other ones first?

comment:17 Changed 20 months ago by Josh

The problem is that generic.ppd and Generic-PostScript_Printer-Postscript.ppd are provided by drivers and don't have real PPD files somewhere on the system so the current detection code won't work.

Would it be sensible to call lpinfo with the make and model identifiers for PDF and postscript instead of re-implementing search code?

comment:18 Changed 20 months ago by Antoine Martin

That explains why I couldn't find them!

We could parse the lpinfo output I guess. Execing external commands is always a bit problematic (they can hang, the output format can change, etc..), but I can't think of another way.

Some questions you may be able to help me with:

  • what would the lpadmin command look like for using the "Generic Postscript Printer" ppd since there is no file?
  • packaging: what is the debian / ubuntu package we can depend on?

PS: r10444 updates the rpm and deb package dependencies to include "cups-filters" as well as "cups-pdf" (which we may be able to drop later?).

comment:19 Changed 20 months ago by Josh

Sorry for the delay in getting back to this.


what would the lpadmin command look like for using the "Generic Postscript Printer" ppd since there is no file?


It seems to just use the -m switch a la lpadmin -p "testps" -m drv:///sample.drv/generic.ppd


packaging: what is the debian / ubuntu package we can depend on?


As far as i can tell (i.e. dpkg -S) we should depend on "cups-filters" to get the PDF ppd and the Postscript driver in sample.drv is provided by "cups-common".

comment:20 Changed 20 months ago by Antoine Martin

Owner: changed from Josh to Antoine Martin
Status: newassigned

Thanks, I'll get on it tomorrow.

comment:21 Changed 20 months ago by Antoine Martin

@joshiggins: just one more thing... since there is no ppd file we can check, how do we know if the lpadmin command is going to succeed at runtime?

comment:22 Changed 20 months ago by Antoine Martin

Owner: changed from Antoine Martin to Josh
Status: assignednew

Mostly implemented in r10582 + some later fixups (made a mess with too many uncommited changes sitting in my local tree), see commit message for details.

This new "generic printers" code can be disabled using the env var XPRA_PRINTERS_GENERIC=0.

I had to add config file options so we can pre-define the printer ppd files / driver strings because running "lpinfo" every time more than doubled the server statup time! (this is also a bit more user friendly than the env var hack it supersedes)
Mine now looks like this:

$ grep "printer =" /etc/xpra/xpra.conf  | grep -v "^#"
postscript-printer = drv:///sample.drv/generic.ppd
pdf-printer = /usr/share/ppd/cupsfilters/Generic-PDF_Printer-PDF.ppd

@joshiggins: I can't seem to get any meaningful information out of lpinfo for the generic PDF printer:

$ lpinfo --make-and-model 'Generic PDF Printer' -m 
lsb/opt/cupsfilters/Generic-PDF_Printer-PDF.ppd Generic PDF Printer
lsb/usr/cupsfilters/Generic-PDF_Printer-PDF.ppd Generic PDF Printer

Those paths are invalid! (and I have absolutely no idea how we are supposed to convert them into valid paths)

So the generic PDF printer we detect is still based on detected cupsfilters paths rather than the "lpinfo" output.
(I guess I could split that string to figure out the ppd file name we should be looking for - meh)

You can run the pycups script from the command line to see what gets detected:

$ ./xpra/platform/pycups_printing.py -v
get_printer_definitions() UNPROBED_PRINTER_DEFS={}, GENERIC=True
get_lpinfo_drv(Generic PostScript Printer) command=['lpinfo', '--make-and-model', 'Generic PostScript Printer', '-m']
lpinfo out=drv:///sample.drv/generic.ppd Generic PostScript Printer\nfoomatic:Generic-PostScript_Printer-Postscript.ppd Generic PostScript Printer Foomatic/Postscript\n
lpinfo err=
get_lpinfo_drv(Generic PDF Printer) command=['lpinfo', '--make-and-model', 'Generic PDF Printer', '-m']
lpinfo out=lsb/opt/cupsfilters/Generic-PDF_Printer-PDF.ppd Generic PDF Printer\nlsb/usr/cupsfilters/Generic-PDF_Printer-PDF.ppd Generic PDF Printer\n
lpinfo err=
pycups settings: PRINTER_DEF={'application/postscript': ['-m', 'drv:///sample.drv/generic.ppd'], 'application/pdf': ['-P', '/usr/share/ppd/cupsfilters/Generic-PDF_Printer-PDF.ppd']}
SELinux is present
SELinux enforce=0
SELinux is present but not in enforcing mode
* application/postscript          : ['-m', 'drv:///sample.drv/generic.ppd']
* application/pdf                 : ['-P', '/usr/share/ppd/cupsfilters/Generic-PDF_Printer-PDF.ppd']

I have tested with a Linux client with:

XPRA_DELETE_PRINTER_FILE=0 \
XPRA_PRINTING_PREFERRED_MIMETYPE="application/postscript" \
    xpra  attach -d printing ...

And then verified:

  • that the server uses the generic "-m" syntax unless XPRA_PRINTERS_GENERIC=0 is used
  • that the client prints correctly
  • that the temporary file is valid postscript
Last edited 20 months ago by Antoine Martin (previous) (diff)

comment:23 Changed 18 months ago by Antoine Martin

Milestone: future0.16

Can we close this for 0.16?

comment:24 Changed 18 months ago by Josh

In the current functionality it's working well for me so yes, I think it can be closed. We can revisit improvements to this in a new ticket.

comment:25 Changed 18 months ago by Josh

Resolution: fixed
Status: newclosed
Note: See TracTickets for help on using tickets.