Δευτέρα 1 Απριλίου 2019

Printing My Pages

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:

  1. 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.
  2. 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.
  3. (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.

  1. 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.]
  2. 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')].
  3. 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.
  4. 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 of link[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.

Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου