This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 22698 - <canvas>: API to make <canvas> prettier when printing
Summary: <canvas>: API to make <canvas> prettier when printing
Status: RESOLVED WONTFIX
Alias: None
Product: WHATWG
Classification: Unclassified
Component: HTML (show other bugs)
Version: unspecified
Hardware: Other All
: P3 enhancement
Target Milestone: Needs Impl Interest
Assignee: Ian 'Hixie' Hickson
QA Contact: contributor
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-07-16 17:37 UTC by Ian 'Hixie' Hickson
Modified: 2017-07-21 10:51 UTC (History)
8 users (show)

See Also:


Attachments

Description Ian 'Hixie' Hickson 2013-07-16 17:37:25 UTC
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.
Comment 1 Ian 'Hixie' Hickson 2014-07-29 00:11:19 UTC
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?
Comment 2 Robert O'Callahan (Mozilla) 2014-07-29 00:20:15 UTC
We have an implementation.
Comment 3 Elliott Sprehn 2014-07-29 05:09:16 UTC
(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.
Comment 4 Ian 'Hixie' Hickson 2014-09-05 22:16:30 UTC
Not sure what to do here. There doesn't appear to be a proposal with multiple vendors supporting it.
Comment 5 Robert O'Callahan (Mozilla) 2014-09-06 04:13:40 UTC
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.
Comment 6 Justin Novosad 2016-04-21 17:01:28 UTC
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.
Comment 7 Ian Kilpatrick 2016-04-21 17:37:32 UTC
+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
Comment 8 Anne 2017-07-21 10:51:28 UTC
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.)