legible notebook printing

Hi Observable folks,

When I try to print a notebook from Safari (I think because of the way Observable uses an iframe to hold notebook content?) the result is basically unusable. Whatever amount of the notebook content fits on each page will print, cutting off halfway down a line of text or KaTeX formula. The default print stylesheet picks a narrow notebook width, so text ends up enormous.

In Firefox it’s even worse: I only get whatever part of the content fits on page 1, followed by a mostly blank page 2 with nothing but the container page’s footer.

I am not at all an expert in browser printing, print CSS stylesheets, or the like, but if you ever get a chance some better default printing would be greatly appreciated!

7 Likes

As a back-up idea: if it turns out to be impossible to get the browser to produce good results by naïvely printing the page, maybe there could be some way to add a separate “turn the current page into a PDF” option somewhere built on http://pdfkit.org or something.

Thank you for this feedback! We will definitely look into this suggestion.

Hey @jrus, observable-prerender might be a solution for this. Under the hood, observable-prerender uses Puppeteer to render notebooks, and Puppeteer can save webpages as PDFs. When trying this out, I added a new .pdf() method and fixed a few bugs (so thanks for the idea!), so here’s how you can do it with one of your notebooks, as of v0.2.0:

const { load } = require("@alex.garcia/observable-prerender");

(async function () {
  const notebook = await load("@jrus/scpie");
  await notebook.pdf("notebook.pdf");
  await notebook.browser.close();
})();


You can see the ouput PDFs in this notebook!

However, it won’t be an exact match of what you see on observablehq.com. observable-prerender embeds the Observable Inspector stylesheet, but there’s more styling that exists on observablehq.com that doesn’t appear to be open-sourced. If you dig around in devtools network tab, you can find the custom stylesheets (which add things like fonts, styling, etc.), but it’s not easily available for non observablehq.com use.

One workaround is to copy-paste that custom stylesheets into the notebook itself. This won’t change anything for the observablehq.com notebook, but it will change the output of the observable-prerender notebook. I did this in this fork of your notebook.

Whipping out node and writing a custom script can be a hassle, though. There is a CSS @media print query that may be helpful, and you might be able to do some more magic with Puppeteer’s pdf() method to really carve out the PDF as you like.

4 Likes

@asg017 It looks like you forgot to share your draft https://observablehq.com/d/7994ba629f00dd3d

1 Like

Ah my bad, fixed

Chiming in as I just had to solve this same problem. I tried exporting a 9-page notebook to a PDF, containing only md & katex (not that it matters much).

Printing directly from the notebook, pagination didn’t work: text was often cut in the middle of a line, or a katex formula chopped up in two.

Printing from the embed iframe would print just the first page and the embed’s footer.

What I ended up doing was to render the notebook on its own with the very basic visionscarto.net/obs/ viewer, and this was good enough in Chrome and Firefox (all the pages and without chopping the formulas).

Firefox was even a bit better since it allows to personalize the page headers and footers, so I could hide the title and url and keep only the page numbers.

2 Likes

Could you guys/folks/fellow O’s share a few problematic notebook URLs (SVG and 2D / 3D canvas are also welcome)? There’s something I’d like to try.

Edit The basic idea is as follows:

  1. Printing is only a problem due to the iframe (no printing from the inside, and printing from the outside sucks). Due to the sandboxing we also cannot open new windows.
  2. However, we can create an object URL and assign that as href to a link:
    html`<a target=_blank
      href="${URL.createObjectURL(new Blob(['<h3 style="color:red">Look, ma, no iframe!'], {type: 
    'text/html'}))}"
    >Click me`
    
  3. Now, using a TreeWalker we can create a copy of the current DOM (or any part of it), turn its outerHTML into a Blob, that Blob into a URL, and open that URL in a new page.
  4. Some elements need special treatment:
    • script should be stripped
    • canvas contents need to be copied over and serialized (object URL + init? background image?)

I haven’t fully tested yet which limitations apply wrt external resources. However, assuming that everything works out as planned (naturally!) we get a few more benefits:

  • inject conditional stylesheets
  • hasslefree printing of subsections
  • can be turned into a bookmarklet, to work on any notebook
  • might be used to save static copies of notebooks? (this one might be a stretch)
2 Likes

This is the biggest problem for sure, but definitely doesn’t make for a perfect experience. Browser printing overall is pretty mediocre when left on its own. (A good print CSS stylesheet might be able to ameliorate some of it?)

It would also be nice to do somewhat smarter layout. E.g. never put a heading at the bottom of a page, never put the last line of a paragraph at the top of a page, try not to put the first few lines of a code listing at the bottom of a page, allow authors to specify a particular state of an animated cell for printing, fix up the margins, possibly add page numbers, …

I put up a github issue:

2 Likes

I just hit this too . Running it through blogify (the static renderer) kinda fixed the issue