Occasionally someone will want to print one of my web pages, even though it means losing access to all the interactive parts. There are a few things I do to make this work better:
- I try to start all my interactive diagrams in a state where it's informative without interaction. This is not only helpful for printing, but also for people skimming the page. I haven't always done this in the past so I've been trying to go back through my older pages and change them to work this way.
- I have a "print stylesheet" using @media print { … } that changes the page style when printing. It changes the font, removes background colors, removes text shadows, and instructs the browser to avoid breaking the page inside a diagram.
- (Added today) When printing, I display the URLs for the links on the page. Since you can't click the links, it's useful to display the URLs.
I use this CSS for printing:
@media print { @page { margin: 1in; } body { font-size: 13pt; font-family: "Book Antiqua", "Times New Roman", serif; } header, footer, h2 { text-shadow: none; color: #000; background-image: unset; background-color: unset; } h2, h3 { page-break-after: avoid; } figure { page-break-inside: avoid; } }
I think it might be simpler to use @media screen { } to set some of the colors instead of trying to undo them with @media print { }.
To display URLs, I first tried this CSS rule:
@media print { a:before { content: "["; } a:after { content: "] (" attr(href) ")"; } }
This makes the links display in Markdown format, as [text](url)
.
Unfortunately, many URLs are quite long. Markdown has a footnote/endnote-style format which works better for long links: [text][1]
followed by [1]: url
. It's not something I can do in pure CSS. I would have to edit the HTML to make this work. But I'm not actually writing HTML directly. I write XHTML that is transformed into HTML using XSLT. Can XSLT do this transformation for me? Yes! I was able to adapt this XSLT technique to work on my pages.
First, create an XSLT rule that applies to all links:
<xsl:template match="//a"> <xsl:copy> <xsl:apply-templates select="node() | @*"/> </xsl:copy> <sup class="print-endnote"> <xsl:number level="any" count="//a" format="[1]"/> </sup> </xsl:template>
This will find links of the form <a href="url">test</a> and turn them into <a href="url">test</a><sup class="print-endnote">[3]</sup>
. The count="//a" parameter to xsl:number
will generate a counter for all <a> elements, and format it with brackets: the third link will be [3]
.
Then, at the bottom of the page, make a list of all the links:
<ul class="print-endnote"> <xsl:apply-templates select="//a" mode="endnote"/> </ul>
This will loop over all elements that match //a, and then apply this template to them:
<xsl:template match="a" mode="endnote"> <li> <xsl:number level="any" count="//a" format="[1]"/>: <xsl:value-of select="@href"/> </li> </xsl:template>
The output will look like this:
<ul class="print-endnote"> <li>[1]: https://…</li> <li>[2]: http://…</li> <li>[3]: http2://…</li> … </ul>
I don't want these to show up when viewing the page on the screen, so I use CSS to hide them by default, and then show them again when printing:
.print-endnote { display: none; } @media print { .print-endnote { display: unset; } }
This works! Throughout the page, links are annotated with a number like link[3]
. Then at the bottom, it displays [3]: url
.
However, as usual, there are details that make things more complicated in practice.
- I want to exclude relative links (which are often to the same page) so I changed the pattern to match a[starts-with(@href,'http')] instead of a. This is in five places; it would be nice to abstract this somehow. [Update 2019-01-01: looks like XLST 2 might let me abstract over this, but XSLT 1 does not.]
- I want to exclude links in the nav bar and table of contents. Due to the page structure I use, these are outside of a
<section>
element, so I changed a to section//a. Combined with the previous rule, it's now the ugly pattern //x:section//a[starts-with(@href,'http')]. - I want to exclude links in the footer, which is inside
<section>
on some of my pages. I did this by adding a test, <xsl:if test="count(ancestor::x:footer) = 0"> for both the endnote marker and the list of urls. These links still receive a number with xsl:number though; I wasn't able to find a way to avoid that. However, since they're at the end of the page, it's not a problem in practice. - All of these rules messed up the weird whitespace handling rules I have in place. I ended up having to make two passes over all the elements, once to expand <a>, and once to apply the whitespace rules. Even then, it is now applying the whitespace in slightly the wrong place. The printed page has
link [3]
instead oflink[3]
. [Update 2019-01-01: I was able to fix this.]
I'm a newbie with XSLT so there's some cargo cult involved. I'm simultaneously impressed that XSLT is able to do this, and horrified by how ugly it is. Someday I hope to revisit all of this, either improving the XSLT or moving away from it, but for now, it works reasonably well, and I'm happy with it.
See screenshots I posted on twitter, or try printing one of my pages to see how it looks. If you run into glitches, please let me know!
Update Here's a slightly different implementation:
Instead of adding a new element in XSLT, add an attribute:
<xsl:template match="//a"> <xsl:copy> <xsl:attribute name="data-endnote"> <xsl:number level="any" count="//a" format="1"/> </xsl:attribute> <xsl:apply-templates select="node() | @*"/> </xsl:copy> </xsl:template>
Then during printing, display that attribute using CSS:
*[data-endnote]:after { color: #000; content: "[" attr(data-endnote) "]"; text-decoration: none; font-size: 75%; position: relative; top: -0.5em; vertical-align: baseline; }
The advantage of this approach is that I don't have to add a new element. The downside is that the underlining of links will apply to the :after
element so these superscripts will be underlined. Another downside is that the superscript is purely visual instead of using a semantic tag like <sup>
. More CSS, less HTML.
Update: [2021-11-22] I added QR codes to the endnotes so that you can scan them instead of typing them in. I added <img><xsl:attribute name="src">https://chart.googleapis.com/chart?chs=120x120&cht=qr&chl=<xsl:value-of select="str:encode-uri(@href, true())"/></xsl:attribute></img>
just after the xsl:number. [Edit: I removed this, as it didn't work well]
Post a Comment