This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.
On Wed, 26 Sep 2012, Julian Viereck wrote: > > This email is about proposing a new attribute "printCallback" on the > HTMLCanvasElement (in the following called "Canvas"). This new API > allows to: > > * define the content of a canvas element during the printing progress > * send the canvas' content without rasterization to the printer > > The basic API was implemented in [1] and is available in Firefox Nightly > 18. > > # Motivation And Use-Case > > The motivation for designing and implementing the API was to add proper > printing support for the PDF.JS project. The PDF.JS project is an > implementation of a PDF viewer using only web technologies. Without this > API it is not possible to: > > * render only the pages needed for printing. A webpage is printed with > the content visible at the moment the print action is started. For > PDF.JS this means that all pages are required to be rendered before > printing. Rendering all the pages takes quite some time for complex and > huge documents (> 100 pages). But the user might only want to print the > first page. That means, the user waits for unnecessary computation to > finish. > > * print the content of a canvas element without rasterization artifacts > on the printout. One could increase the size of the canvas such that the > rasterization doesn't becomes visible, but this is not possible due to > the large usage of memory going with this. Using a different way to > render the pages than using canvas (e.g. SVG) is not possible, due to > memory and performance issues. > > Although not directly relevant to PDF.JS - it's also not possible to > > * define the content of a printed page that looks exactly the same cross > all user agents. There are small variations, that cause breaks and > styles to look slightly different between user agents. Using CSS it's > possible to make one canvas element take up one physical page and then > precisely layout content on the canvas. > > (I will later describe briefly how the API was used to solve these > issues.) > > # Actual API > > The actual API looks like this: > > The "printCallback" attribute on a Canvas takes a callback. This > callback is invoked when the canvas with a printCallback is printed. A > "printState" object is passed as argument to the callback. The > printState object has a "context" property, which points to a > CanvasRenderingContext2D object. Against this context all known > operations of the CanvasRenderingContext2D are executable. However, the > CanvasRenderingContext2D doesn't rasterize the operations but instead > forward them directly to the printer - instead of drawing to a pixel > surface it's more like drawing to a vector surface. The result of the > operations show up on the canvas when printed, but are not visible on > the screen. The "printState.done()" function must be called once all > drawing operations for the canvas are done and the printing should > progress. This was added to allow the printCallback to perform > asynchronous tasks. > > A simple example of the API looks like this: > > var canvas = document.getElementById('canvas'); > var ctx = canvas.getContext('2d'); > ctx.fillText('Hi there.', 50, 50); > > canvas.printCallback = function(printState) { > var printCtx = printState.context; > printCtx.fillText('I\'m only visible when printed.', 50, 50); > printState.done(); > }; > > You can try this example out in [2] using Firefox Nighlty and see the > results as a PDF in [3]. Notice that you can select the text in the PDF > linked in [3]. (Note: in the linked example [2], the callback is called > "mozPrintCallback" as the API is currently prefixed in Gecko. The canvas > output is rasterized on Windows and Linux due to a bug in Gecko at the > moment.) > > Some more details on the behavior of the API: > > * the printCallback function is only invoked on the canvases that will > be visible in the print output. > > * there is only one printCallback called at the time. After the > "printState.done()" function is called, the next printCallback function > gets invoked assuming there is another canvas that gets printed and has > a printCallback specified. > > * the order the printCallbacks of the canvases are called follows the > output order of the canvases in the printout. > > * the resolution on the printContext is the same as when drawing to the > canvas on the page. E.g. if a canvas has the attribute "width" set to > "100" and by using CSS the canvas takes 10 cm in width on the printout, > then 1 unit on the context corresponds to 0.1 cm. > > * the putImageData and getImageData functions on the > CanvasRenderingContext2D use the same pixel resolution (width/height) as > the canvas on the page (this results in the data of the getImageData > function to be rasterized). > > * the "canvas" property on the printContext points to the canvas on the > page and not the canvas element that is printed. Otherwise it's possible > to change the layout of the printing while printing. As the canvas on > the page might not be available anymore (e.g. the canvas was removed and > garbage collected from the document before the printCallback gets > invoked), the "canvas" property might be "undefined" or "null". > > * the window.onafterprint event is called either > 1. after all printCallbacks are done and the page is ready for > printing > 2. the printing got aborted using the print dialog. > > * the printContext holds no content when passed to the callback and > takes the default values (for transformation matrix, styles etc.) of a > CanvasRenderingContext2D > > * the printContext is a CanvasRenderingContext2D but instead of using a > pixel map to store the drawing operations result, the operations are > forwarded to the printer without rasterization. > > * the API does not change the layout of the canvas element on the page. > > # Open Discussion: > > * There is no way to abort in case something goes wrong. E.g. printing > to the canvas might require a successful network request, but the > request failed. > > * The printState.done() function gets eventually never called and > therefore printing the document might never finish. > > # How The PrintCallback-API Solved The Problems For PDF.JS > > * using CSS all content except a single div is hidden during printing > > * when the beforePrint event is fired it is checked if the webpage is > setup for printing or not. If it is not, the webpage is setup and the > print action is canceled. Otherwise, the printing is not prevented and > happens as regular > > * the "setup webpage for printing" consists of the following steps > 1. for each page of the PDF document, a canvas element is created and > insert to the div that is visible during printing > 2. using CSS, the canvas inside the print-visible div take up an entire > page in the later printout > 3. for each canvas the `mozPrintCallback` is set. If the callback > function is called, the pdf page corresponding to the canvas is loaded > and drawn on the canvas. Once finished, the `printState.done()` function > is called > > * after the user set the print settings in the print dialog, the webpage > is printed > > * for the pages that are required for the printout, the mozPrintCallback > is called on the canvas for these pages > > * after the printing finished (detected by listening to the afterPrint > event), the created canvases are removed again to save memory. > > [1]: Mozilla Bug 745025 - Implement CanvasElement.mozPrintCallback: > https://bugzilla.mozilla.org/show_bug.cgi?id=745025 > [2]: http://jsfiddle.net/FPNMM/2/embedded/js%2Cresult/ > [3]: http://n.ethz.ch/~jviereck/drop/mozPrintCallback_output.pdf On Wed, 23 Jan 2013, Julian Viereck wrote: > > Discussing the proposal with Robert "roc" O'Callahan, we came up with > the following adjustments. They are targeting the "Some more details on > the behavior of the API" from before: > > (1) With the previous proposal, printCallbacks are executed even after > the window is unloaded. We think this is not a good idea. There > shouldn't be any JavaScript execution after a window is unloaded. > Therefore let's add the following: > > Encourage the UA to prevent closing the window while print callbacks > are pending. If the window is nevertheless closed and while some > printCallbacks have not completed yet, all printCallbacks are > canceled, the JavaScript execution is stopped and the print job is > aborted. Canceling the printCallbacks is done to prevent any > JavaScript execution after the window is unloaded. > > (2) Given the change in (1), we can now change the following point: > > > * the "canvas" property on the printContext points to the canvas on > > the page and not the canvas element that is printed. Otherwise it's > > possible to change the layout of the printing while printing. As the > > canvas on the page might not be available anymore (e.g. the canvas was > > removed and garbage collected from the document before the > > printCallback gets invoked), the "canvas" property might be > > "undefined" or "null". > > to: > > * the "canvas" property on the printContext points to the canvas on the > page and not the canvas element that is printed. Otherwise it's possible > to change the layout of the printing while printing. > > Simply saying, if the window object is always alive while the > printCallbacks are happening (thanks to (1)), the "canvas" property on > the printContext can always point to the canvas on the page. On Mon, 28 Jan 2013, Elliott Sprehn wrote: > > 1) I feel like this should probably be an event. I don't know why we're > inventing new callback facilities everywhere. > > canvas.onprintcanvas = function(e) { e.printState ... } On Thu, 7 Feb 2013, Julian Viereck wrote: > > A event might be dispatched to multiple listeners. In the scenario with > the print callback it makes only sense to have one function that defines > the look of the canvas during printing. Therefore I've choosen to not > use the event-listener pattern but instead a single callback function. On Thu, 7 Feb 2013, Elliott Sprehn wrote: > > It should be an event. There are legitimate reasons for multiple > listeners, for instance I might have a listener on body that adds > watermarks and a listener on the canvas that does just the drawing. > > Not using events also limits my ability to globally hook the facility. > For instance I'd like to do: > > $(document).printcanvas(function(e) { > if (drawingFunctions[e.target.id]) > drawingFunctions[e.target.id](e.target); > }); On Mon, 28 Jan 2013, Elliott Sprehn wrote: > > 2) What does "send the canvas' content without rasterization to the > printer" mean? How are blending and overlapping images handled? Your > current description makes it sound like if I did two drawImage() calls > it would make my printer print the images on top of each other. On Thu, 7 Feb 2013, Elliott Sprehn wrote: > > [...] you don't mean sent to the physical printer without rasterization > then, you mean it's just spooled to the printer driver which may > rasterize it to send it to the device. That seems to run into operating > system and implementation limitations. I don't know if the spec should > require that behavior. On Mon, 28 Jan 2013, Elliott Sprehn wrote: > > 3) If we're advocating that developers "put a canvas on every page that > covers the whole page" as the standard way to handle large document > printing why not have a handler that gets given a canvas for every page > automatically instead of requiring the developer to insert it > themselves. This seems much easier, and handles the memory management > for since we can drop the backing buffer between events for each page. > > document.onprintpage = function(e) { > e.index // number of the page > e.range // range that encompasses all the nodes to print > e.canvas // canvas to send drawing commands to for printing > }; On Tue, 29 Jan 2013, Robert O'Callahan wrote: > > Another use-case is when you have a document that's regular HTML and > just happens to contain one or more <canvas> images that should be > beautiful when printed. On Tue, 29 Jan 2013, Elliott Sprehn wrote: > > Why can't I do this for <video> or <object> or any number of other > things then? All those things want to be beautiful when printed too. :) > > In that world I really think we want something closer to > Element#printCallback. On Thu, 7 Feb 2013, Elliott Sprehn wrote: > > [...] Why can't I do the same thing for <img> ? Cocoa lets me hook > NSView drawRect: and draw whatever I want at print time. On Tue, 29 Jan 2013, Robert O'Callahan wrote: > > That doesn't make sense to me. <canvas> is special because we have APIs > to draw into it, and authors can use the same code that draws a screen > canvas to draw a beautiful print canvas with very few changes. The > contents of <video> and <object> aren't rendered by author JS code, so > it's not natural to provide an API that lets them be rendered by author > JS code just for the print case. However it is possible with our > proposal for authors to use some clever @media CSS to replace any > element with a pretty <canvas> version if they need to. > > Also, <video> and <object> (and <img>) have features that could be used > to have them print nicely without author script having to take over > their drawing. For example, on some platforms a plugin in an <object> > can define custom printing behavior. Presumably <img srcset> also provides a solution for printing. On Mon, 28 Jan 2013, Elliott Sprehn wrote: > > 4) SVG is for vector graphics, not canvas. Why can't I replace an entire > page with an <svg> instead of drawing to a canvas? :) On Tue, 29 Jan 2013, Robert O'Callahan wrote: > > SVG doesn't fit some use-cases very well, for example, when you generate > graphics from some underlying (large) data model. On Tue, 29 Jan 2013, Elliott Sprehn wrote: > > Vector graphics is exactly what you want when the data model is really > big though since it lets you prune out things you know can't be seen and > stream the commands directly to hardware without rasterization as > they're attempting to do with <canvas> magic in this proposal. > > If SVG is broken for this use case we should fix that, it seems really > sad to "give up" on SVG and bolt vector graphics onto <canvas>. On Tue, 29 Jan 2013, Robert O'Callahan wrote: > > In our prototype implementation in Firefox, when drawing in a > printCallback and printing to a PDF, if the author's canvas drawing > calls can be directly supported by PDF then they are emitted into the > PDF and not rasterized at all. > > If you have a very large data model to render, converting directly from > the data model to (GL or 2D) drawing commands is a lot more efficient > than building a persistent CSS-styled DOM as an intermediate step. > Having the browser optimize away that intermediate step doesn't seem > feasible.
roc, esprehn: I assume Mozilla and Chrome are both on board here? Do either of you have an implementation of this yet that I should reverse-engineer?
We have an implementation.
(In reply to Ian 'Hixie' Hickson from comment #1) > roc, esprehn: I assume Mozilla and Chrome are both on board here? Do either > of you have an implementation of this yet that I should reverse-engineer? We haven't implemented anything like this, but I think some kind of "print" event for <canvas> would be reasonable given the use cases here. I don't think we should add a callback though.
Not sure what to do here. There doesn't appear to be a proposal with multiple vendors supporting it.
I'm happy to consider any counter-proposal here that meets the needs of applications like pdf.js. "Just use SVG" is not acceptable for performance reasons for the reasons quoted from me in comment #0. I'm happy to change the callback function to an event as per Elliott's comments. I don't think having multiple receivers working together to paint the canvas makes sense, but I also don't think it's worth blocking the feature on that point. I don't know what other objections Elliott (or anyone else) has, that haven't already been adequately addressed.
I believe this issue is being addressed indirectly by the css paint api: https://drafts.css-houdini.org/css-paint-api/ For a project like PDF.js, there is one big gap in the css paint feature though: it does not do text rendering.
+1 CSS Paint API Should be able to handle this nicely. CSS Paint API doesn't assume the resolution which the image will be rastered at, so the UA can upscale / downscale as they see fit for printing / scaling / etc. We stripped out Fonts for CSS Paint API initially, but they should be a small add-on once we figure out the font resolution behavior in paint. - Ian
If this is still desired, please file a new issue at https://github.com/whatwg/html/issues/new. (Seems like it isn't from the last couple of comments.)