Clickbank Products

Tuesday, November 19, 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.

No comments:

Post a Comment