--- author: email: mail@petermolnar.net image: https://petermolnar.net/favicon.jpg name: Peter Molnar url: https://petermolnar.net copies: - http://web.archive.org/web/20180419080453/https://petermolnar.net/how-to-make-a-print-css/ lang: en published: '2018-04-17T19:00:00+01:00' summary: CSS snippets to consider when you want to provide a printable version of your blog tags: - CSS title: Guide on how to make your website printable with CSS --- ## Printing?! It's 2018! "Printing" doesn't always mean putting it on paper. When people print a web article, sometimes it ends up as a PDF, because the HTML save it not usable. The reasons for this differ: JavaScript rendered content, unsaved scripts in the end result, the lack of MHTML support in browsers, etc. What's important is that providing a print-friendly format for your site makes it possible for people to save it in a usable way. Printing might still be relevant, because that's the only method that gives you a physical object. I have long entries about journeys, visits of foreign places. At a certain point in time I was tempted to put together a photobook from the images there, but the truth is: it's a lot of work, especially if you've more or less done it already once by writing your entry. There's also the completely valid case of archiving: hard copies have a life of decades if not centuries, when stored properly, unlike any electronic media we currently have as an option. ## That little extra CSS Before jumping into the various hacks that helps printers it's important to mention, how to add printer-only instructions to your CSS. There are two ways, either using: ``` {.css} @media print { } ``` inside an existing CSS, or by adding another CSS file specifically for `print` media into the HTML `<head>` section: ``` {.html} <head> <link rel="stylesheet" type="text/css" href="print.css" media="print"> </head> ``` ## Recommended styling ### White background, black(ish) text Most printers operate with plain, white paper, so unless there's a very, very good reason for printing background color, just get rid of it. It also applies to the font: a bit lighter from black, so saves tint. ``` {.css} * { background-color: #fff !important; color: #222; } ``` ### Use printer and PDF safe fonts If you take a look at the history of printers vs fonts there used to be many problems around this topic - even so they might still require a font cartridge to be able to properly print fonts out of the basic options.[^1] To avoid rendering problems, aliasing issues, generally speaking: unreadable fonts, stick to one of the base 14 fonts: - Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique - Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique - Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic - Symbol - ZapfDingbats which are, by definition, part of the PDF standard[^2]. So for example: ``` {.css} * { font-size: 11pt !important; font-family: Helvetica, sans-serif !important; } ``` If you do insist on special fonts, eg. you have icons in fonts, you might want to consider using SVG instead of fonts for icons - otherwise printing them properly will become a problem. Besides the potential printing issues one more reason to go with a standard, base font is that if for any reason the text needs to go through character recognition for scanning it back - say it's an archival hard copy and the only one left after a data loss indicent - the simpler and wider known the font, the better your chances for getting the characters properly recognized. ### Pages and page breaks It's very annoying to find a heading at the bottom of a printed page, or a paragraph broke into separate pages, although this latter depends on paragraph length. I generally recommend disallowing page breaks at these locations. Apart from this it's a good idea to have a margin around the edges so you have an area where you can handle the page, not covering any of the text, or where it can be glued together as pages in a book. ``` {.css} @page { margin: 0.5in; } h1, h2, h3, h4, h5, h6 { page-break-after: avoid !important; } p, li, blockquote, figure, img { page-break-inside: avoid !important; } ``` ### Images Printing images is tricky: most of the images are sized for the web and those sizing are too small by resolution, too large by percentage of space taken for printing. The alt-text and the image headline, which is usually in `alt` and `title` are also something to consider printing, but unfortunately the `href` trick doesn't work with them: that is because you can't add `::before` or `::after` to self-closing tags, such as images. Lately, instead of using simple `img` tags, I switched to using `figure`, along with `figcaption` - this way the headline became possible to print. Apart from this I've limited the size of the images by view-width and view-height, so they never become too large and occupy complete pages. ``` {.css} figure { margin: 1rem 0; } figcaption { text-align: left; margin: 0 auto; padding: 0.6rem; font-size: 0.9rem; } figure img { display: block; max-height: 35vh; max-width: 90vw; outline: none; width: auto; height: auto; margin: 0 auto; padding: 0; } ``` This is how `images` inside `figure` (should) look in print with the styling above: ![This is how images can look like when some width/height limitations are applied in printing](css-print-example-photos.jpg) ### Source codes If you have code blocks in your page it's useful to have them coloured, but still dark-on-light. I'm using Pandoc's built-in syntax highlighting[^3] and the following styling for printing: ``` {.css} code, pre { max-width: 96%; border: none; color: #222; word-break: break-all; word-wrap: break-word; white-space: pre-wrap; overflow:initial; page-break-inside: enabled; font-family: "Courier", "Courier New", monospace !important; } pre { border: 1pt dotted #666; padding: 0.6em; } /* code within pre - this is to avoid double borders */ pre code { border: none; } code.sourceCode span { color: black; } code.sourceCode span.al { color: black; } code.sourceCode span.at { color: black; } code.sourceCode span.bn { color: black; } code.sourceCode span.bu { color: black; } code.sourceCode span.cf { color: black; } code.sourceCode span.ch { color: black; } code.sourceCode span.co { color: darkgray; } code.sourceCode span.dt { color: black; } code.sourceCode span.dv { color: black; } code.sourceCode span.er { color: black; } code.sourceCode span.ex { color: darkorange; } code.sourceCode span.fl { color: black; } code.sourceCode span.fu { color: darkorange; } code.sourceCode span.im { color: black; } code.sourceCode span.kw { color: darkcyan; } code.sourceCode span.op { color: black; } code.sourceCode span.ot { color: black; } code.sourceCode span.pp { color: black; } code.sourceCode span.sc { color: black; } code.sourceCode span.ss { color: black; } code.sourceCode span.st { color: magenta; } code.sourceCode span.va { color: darkturquoise; } ``` It should result in something similar: ![Color printing source code](css-print-example-sourcecode.png) ### Printing links and theirs URLs #### The basic CSS solution Links are the single most important things on the internet; they are the internet. However, when they get printed, the end result usually looks something like this: ![Before showing URLs - example showing Wikipedia entry "Mozilla software rebranded by Debian"](css-print-example-urls-before.png) In order to avoid this problem, the URLs behind the links need to be shown as if they were part of the text. There is a rather simple way to do it: ``` {.css} a::after { content: " (" attr(href) ") "; font-size: 90%; } ``` but unfortunately it makes the text rather ugly and very hard to read: ![After showing URLs](css-print-example-urls-after.png) #### Aaron Gustafson's solution[^4] {#aaron-gustafsons-solution4} There is a very nice, minimalistic Javascript solution[^5] that collects all links on the page and converts them into footnotes on the fly, when it detects a print request. This solution is way nicer, so I certainly recommend using this as well (it's a supplement for the CSS solution above) even if it requres Javascript: (this is a copy-paste solution, just put it in your header) ``` {.html} <script type="text/javascript"> // <![CDATA[ /*------------------------------------------------------------------------------ Function: footnoteLinks() Author: Aaron Gustafson (aaron at easy-designs dot net) Creation Date: 8 May 2005 Version: 1.3 Homepage: http://www.easy-designs.net/code/footnoteLinks/ License: Creative Commons Attribution-ShareAlike 2.0 License http://creativecommons.org/licenses/by-sa/2.0/ Note: This version has reduced functionality as it is a demo of the script's development ------------------------------------------------------------------------------*/ function footnoteLinks(containerID,targetID) { if (!document.getElementById || !document.getElementsByTagName || !document.createElement) return false; if (!document.getElementById(containerID) || !document.getElementById(targetID)) return false; var container = document.getElementById(containerID); var target = document.getElementById(targetID); var h2 = document.createElement('h2'); addClass.apply(h2,['printOnly']); var h2_txt = document.createTextNode('Links'); h2.appendChild(h2_txt); var coll = container.getElementsByTagName('*'); var ol = document.createElement('ol'); addClass.apply(ol,['printOnly']); var myArr = []; var thisLink; var num = 1; for (var i=0; i<coll.length; i++) { var thisClass = coll[i].className; if ( coll[i].getAttribute('href') || coll[i].getAttribute('cite') ) { thisLink = coll[i].getAttribute('href') ? coll[i].href : coll[i].cite; var note = document.createElement('sup'); addClass.apply(note,['printOnly']); var note_txt; var j = inArray.apply(myArr,[thisLink]); if ( j || j===0 ) { note_txt = document.createTextNode(j+1); } else { var li = document.createElement('li'); var li_txt = document.createTextNode(thisLink); li.appendChild(li_txt); ol.appendChild(li); myArr.push(thisLink); note_txt = document.createTextNode(num); num++; } note.appendChild(note_txt); if (coll[i].tagName.toLowerCase() == 'blockquote') { var lastChild = lastChildContainingText.apply(coll[i]); lastChild.appendChild(note); } else { coll[i].parentNode.insertBefore(note, coll[i].nextSibling); } } } target.appendChild(h2); target.appendChild(ol); addClass.apply(document.getElementsByTagName('html')[0],['noted']); return true; } window.onload = function() { footnoteLinks('content','content'); } // ]]> </script> <script type="text/javascript"> // <![CDATA[ /*------------------------------------------------------------------------------ Excerpts from the jsUtilities Library Version: 2.1 Homepage: http://www.easy-designs.net/code/jsUtilities/ License: Creative Commons Attribution-ShareAlike 2.0 License http://creativecommons.org/licenses/by-sa/2.0/ Note: If you change or improve on this script, please let us know. ------------------------------------------------------------------------------*/ if(Array.prototype.push == null) { Array.prototype.push = function(item) { this[this.length] = item; return this.length; }; }; // --------------------------------------------------------------------- // function.apply (if unsupported) // Courtesy of Aaron Boodman - http://youngpup.net // --------------------------------------------------------------------- if (!Function.prototype.apply) { Function.prototype.apply = function(oScope, args) { var sarg = []; var rtrn, call; if (!oScope) oScope = window; if (!args) args = []; for (var i = 0; i < args.length; i++) { sarg[i] = "args["+i+"]"; }; call = "oScope.__applyTemp__(" + sarg.join(",") + ");"; oScope.__applyTemp__ = this; rtrn = eval(call); oScope.__applyTemp__ = null; return rtrn; }; }; function inArray(needle) { for (var i=0; i < this.length; i++) { if (this[i] === needle) { return i; } } return false; } function addClass(theClass) { if (this.className != '') { this.className += ' ' + theClass; } else { this.className = theClass; } } function lastChildContainingText() { var testChild = this.lastChild; var contentCntnr = ['p','li','dd']; while (testChild.nodeType != 1) { testChild = testChild.previousSibling; } var tag = testChild.tagName.toLowerCase(); var tagInArr = inArray.apply(contentCntnr, [tag]); if (!tagInArr && tagInArr!==0) { testChild = lastChildContainingText.apply(testChild); } return testChild; } // ]]> </script> <style type="text/css" media="screen"> .printOnly { display: none; } </style> <style type="text/css" media="print"> a:link:after, a:visited:after { content: " (" attr(href) ") "; font-size: 90%; } html.noted a:link:after, html.noted a:visited:after { content: ''; } </style> ``` #### Alternative approach: always using footnotes for URLs I little while ago I made a decision to put all links into footnotes by default - no in-text-links which will bring you to another site. This is a design decision and doesn't apply to most of the already existing sites, but if you, just as me, think, there is value in it, consider it as an option. It also makes the two hacks above obsolete, however, it has it's own problems, such as reading the site entries via RSS. ## Avoid ### opacity and transparency: it can get blurry A simple and sort of lazy solution to, instead of figuring out the proper color code, just apply opacity to a text to make it slightly different from the rest. Unfortunately some of these opacity settings can result in blurry or unusable text: ![CSS opacity resulting in blurry text](css-print-example-opacity.png) Therefore I suggest to avoid opacity and transparency on all elements for your printing styles. Happy printing! [^1]: <https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/print_c_fonts.mspx> [^2]: <https://en.wikipedia.org/wiki/Portable_Document_Format#Standard_Type_1_Fonts_.28Standard_14_Fonts.29> [^3]: <http://pandoc.org/MANUAL.html#syntax-highlighting> [^4]: <https://alistapart.com/article/improvingprint> [^5]: <https://alistapart.com/d/improvingprintfinal.html>