twitter bootstrap icons are missing in print
Glyphicons are missing when I hit 'Print', but are shown correctly in browser window. I'm talking about a simple page with static content, except for latest twitter bootstrap.
Is it possible to get Bootstrap icons shown in print?
FULL CSS SOLUTION
I have written a CSS print stylesheet solution that should solve 80-90% of this problem occurring for sites that require the icons(glyphicons) from bootstrap to show up when printing without requiring the user to turn on "print background images" in their browser and this solution will work in ALL major browsers(Chrome, Safari, Firefox, IE).
This solution is detailed referencing the bootstrap issue specifically but it should be possible leverage it for other similar issues of background images not printing when needed. It also assumes you are using a separate @media print {}
stylesheet. I'll explain 10-20% of situations it doesn't solve and why at the end(as well as a fix for these occurrences).
The issue of background-image
, background-position
and width height
properties being used exclusively to position and size sprite images is solved by replacing with the properties content: url()
, clip: rect()
, margin-top
and margin-left
along with some overrides.
In my case we were using the <i class="icon-globe"></i>
to show links for courses available internationally and so users frequently printed this list but the important indicating information was removed by the browser. I had found the solution of copying all the CSS to display the icons into our print stylesheet along with adding the property value
-webkit-print-color-adjust:exact;
to the
[class^="icon-"], [class*=" icon-"] { background-image: url("../img/glyphicons-halflings.png"); background-position: 14px 14px; background-repeat: no-repeat... }
but this only solved the problem in Chrome and Safari with no indication from the community that Firefox or IE would be supporting this webkit property any time soon.
So we need to completely change how <i class="icon-globe"></i>
is rendered when the page is sent to the printer driver by the browser.
The standard method with executing sprites is to declare a visible opening space(14px by 14px in this case) and then reposition the background-image
behind that space so the appropriate icon can show through.
To effectively replicate this in the foreground we will use the content: url()
to call the image and then clip: rect()
to cut this image down to the appropriate icon and then use negative values in margin-top
and margin-left
to position the new foreground image back where the background image icon had originally been positioned.
A difficulty is cutting the image down using clip requires 4 coordinates(top, right, bottom, left) while background-position
only requires 2 coordinates(xpos, ypos - the pixel distances from the top left corner). The other difficulty in using the clip
property is that unlike padding
or margin
these coordinates are not calculated from their respective outside border in but from the top and left sides only which actually makes our math conversion from background-position
a little easier but may take some time to get used to.
More info on clip property
(ran out of links due to my low reputation so you'll need to figure out what sneaky thing I've done)
www.ibloomstudios[dot]com/articles/misunderstood_css_clip/
css-tricks[dot]com/css-sprites-with-inline-images/
tympanus[dot]net/codrops/2013/01/16/understanding-the-css-clip-property/
THE ACTUAL CODE
Getting back to the <i class="icon-globe"></i>
example, we want to convert
[class^="icon-"], [class*=" icon-"] { display: inline-block; width: 14px; height: 14px; *margin-right: .3em; line-height: 14px; vertical-align: text-top; background-image: url("../img/glyphicons-halflings.png"); background-position: 14px 14px; background-repeat: no-repeat; } //skipping other icons... .icon-globe { background-position: -336px -144px; }
into this in the print stylesheet
i[class^="icon-"], i[class*=" icon-"] { display: inline-block; vertical-align: text-top; width: 14px; background-image:none!important; background-repeat:no-repeat; background-position: 0 0!important; } //skipping other icons... i.icon-globe::after { clip: rect(144px 350px 158px 336px)!important; margin-left: -336px!important; margin-top: -144px!important; content: url('../img/glyphicons-halflings.png')!important; position:absolute!important; width:auto!important; height:auto!important; }
We can see that taking the background-position(xpos & ypos OR left & top) coordinates and changing to positives are the same as clip: rect(top, left+14px, top+14px, left).
Then we use the original negative background-position: left top;
as margin-left
and margin-top
.
This CSS also includes some !important
overrides in case the original bootstrap icon is displayed over top of our new clipped image which is stripped out upon printing.
That worked for the globe-icon and solved my specific problem but then I wondered how many other indicating icons were not being printed and so I used some clever replace all commands in notepad to create a single line version of the bootstrap icon CSS and tab delimited each element (plus added some px to the zeros so columns would line up)...
.icon-glass { background-position: 0 px 0 px ; } .icon-music { background-position: -24 px 0 px ; } .icon-search { background-position: -48 px 0 px ; } .icon-envelope { background-position: -72 px 0 px ; } .icon-heart { background-position: -96 px 0 px ; } .icon-star { background-position: -120 px 0 px ; } .icon-star-empty { background-position: -144 px 0 px ; } .icon-user { background-position: -168 px 0 px ; } .icon-film { background-position: -192 px 0 px ; } .icon-th-large { background-position: -216 px 0 px ; } .icon-th { background-position: -240 px 0 px ; } .icon-th-list { background-position: -264 px 0 px ; } .icon-ok { background-position: -288 px 0 px ; } .icon-remove { background-position: -312 px 0 px ; } .icon-zoom-in { background-position: -336 px 0 px ; } .icon-zoom-out { background-position: -360 px 0 px ; } .icon-off { background-position: -384 px 0 px ; } .icon-signal { background-position: -408 px 0 px ; } .icon-cog { background-position: -432 px 0 px ; } .icon-trash { background-position: -456 px 0 px ; } .icon-home { background-position: 0 px -24 px ; } .icon-file { background-position: -24 px -24 px ; } .icon-time { background-position: -48 px -24 px ; } .icon-road { background-position: -72 px -24 px ; } .icon-download-alt { background-position: -96 px -24 px ; } .icon-download { background-position: -120 px -24 px ; } .icon-upload { background-position: -144 px -24 px ; } .icon-inbox { background-position: -168 px -24 px ; } .icon-play-circle { background-position: -192 px -24 px ; } .icon-repeat { background-position: -216 px -24 px ; } .icon-refresh { background-position: -240 px -24 px ; } .icon-list-alt { background-position: -264 px -24 px ; } .icon-lock { background-position: -287 px -24 px ; } .icon-flag { background-position: -312 px -24 px ; } .icon-headphones { background-position: -336 px -24 px ; } .icon-volume-off { background-position: -360 px -24 px ; } .icon-volume-down { background-position: -384 px -24 px ; } .icon-volume-up { background-position: -408 px -24 px ; } .icon-qrcode { background-position: -432 px -24 px ; } .icon-barcode { background-position: -456 px -24 px ; } .icon-tag { background-position: 0 px -48 px ; } .icon-tags { background-position: -25 px -48 px ; } .icon-book { background-position: -48 px -48 px ; } .icon-bookmark { background-position: -72 px -48 px ; } .icon-print { background-position: -96 px -48 px ; } .icon-camera { background-position: -120 px -48 px ; } .icon-font { background-position: -144 px -48 px ; } .icon-bold { background-position: -167 px -48 px ; } .icon-italic { background-position: -192 px -48 px ; } .icon-text-height { background-position: -216 px -48 px ; } .icon-text-width { background-position: -240 px -48 px ; } .icon-align-left { background-position: -264 px -48 px ; } .icon-align-center { background-position: -288 px -48 px ; } .icon-align-right { background-position: -312 px -48 px ; } .icon-align-justify { background-position: -336 px -48 px ; } .icon-list { background-position: -360 px -48 px ; } .icon-indent-left { background-position: -384 px -48 px ; } .icon-indent-right { background-position: -408 px -48 px ; } .icon-facetime-video { background-position: -432 px -48 px ; } .icon-picture { background-position: -456 px -48 px ; } .icon-pencil { background-position: 0 px -72 px ; } .icon-map-marker { background-position: -24 px -72 px ; } .icon-adjust { background-position: -48 px -72 px ; } .icon-tint { background-position: -72 px -72 px ; } .icon-edit { background-position: -96 px -72 px ; } .icon-share { background-position: -120 px -72 px ; } .icon-check { background-position: -144 px -72 px ; } .icon-move { background-position: -168 px -72 px ; } .icon-step-backward { background-position: -192 px -72 px ; } .icon-fast-backward { background-position: -216 px -72 px ; } .icon-backward { background-position: -240 px -72 px ; } .icon-play { background-position: -264 px -72 px ; } .icon-pause { background-position: -288 px -72 px ; } .icon-stop { background-position: -312 px -72 px ; } .icon-forward { background-position: -336 px -72 px ; } .icon-fast-forward { background-position: -360 px -72 px ; } .icon-step-forward { background-position: -384 px -72 px ; } .icon-eject { background-position: -408 px -72 px ; } .icon-chevron-left { background-position: -432 px -72 px ; } .icon-chevron-right { background-position: -456 px -72 px ; } .icon-plus-sign { background-position: 0 px -96 px ; } .icon-minus-sign { background-position: -24 px -96 px ; } .icon-remove-sign { background-position: -48 px -96 px ; } .icon-ok-sign { background-position: -72 px -96 px ; } .icon-question-sign { background-position: -96 px -96 px ; } .icon-info-sign { background-position: -120 px -96 px ; } .icon-screenshot { background-position: -144 px -96 px ; } .icon-remove-circle { background-position: -168 px -96 px ; } .icon-ok-circle { background-position: -192 px -96 px ; } .icon-ban-circle { background-position: -216 px -96 px ; } .icon-arrow-left { background-position: -240 px -96 px ; } .icon-arrow-right { background-position: -264 px -96 px ; } .icon-arrow-up { background-position: -289 px -96 px ; } .icon-arrow-down { background-position: -312 px -96 px ; } .icon-share-alt { background-position: -336 px -96 px ; } .icon-resize-full { background-position: -360 px -96 px ; } .icon-resize-small { background-position: -384 px -96 px ; } .icon-plus { background-position: -408 px -96 px ; } .icon-minus { background-position: -433 px -96 px ; } .icon-asterisk { background-position: -456 px -96 px ; } .icon-exclamation-sign { background-position: 0 px -120 px ; } .icon-gift { background-position: -24 px -120 px ; } .icon-leaf { background-position: -48 px -120 px ; } .icon-fire { background-position: -72 px -120 px ; } .icon-eye-open { background-position: -96 px -120 px ; } .icon-eye-close { background-position: -120 px -120 px ; } .icon-warning-sign { background-position: -144 px -120 px ; } .icon-plane { background-position: -168 px -120 px ; } .icon-calendar { background-position: -192 px -120 px ; } .icon-random { background-position: -216 px -120 px ; } .icon-comment { background-position: -240 px -120 px ; } .icon-magnet { background-position: -264 px -120 px ; } .icon-chevron-up { background-position: -288 px -120 px ; } .icon-chevron-down { background-position: -313 px -119 px ; } .icon-retweet { background-position: -336 px -120 px ; } .icon-shopping-cart { background-position: -360 px -120 px ; } .icon-folder-close { background-position: -384 px -120 px ; } .icon-folder-open { background-position: -408 px -120 px ; } .icon-resize-vertical { background-position: -432 px -119 px ; } .icon-resize-horizontal { background-position: -456 px -118 px ; } .icon-hdd { background-position: 0 px -144 px ; } .icon-bullhorn { background-position: -24 px -144 px ; } .icon-bell { background-position: -48 px -144 px ; } .icon-certificate { background-position: -72 px -144 px ; } .icon-thumbs-up { background-position: -96 px -144 px ; } .icon-thumbs-down { background-position: -120 px -144 px ; } .icon-hand-right { background-position: -144 px -144 px ; } .icon-hand-left { background-position: -168 px -144 px ; } .icon-hand-up { background-position: -192 px -144 px ; } .icon-hand-down { background-position: -216 px -144 px ; } .icon-circle-arrow-right { background-position: -240 px -144 px ; } .icon-circle-arrow-left { background-position: -264 px -144 px ; } .icon-circle-arrow-up { background-position: -288 px -144 px ; } .icon-circle-arrow-down { background-position: -312 px -144 px ; } .icon-globe { background-position: -336 px -144 px ; } .icon-wrench { background-position: -360 px -144 px ; } .icon-tasks { background-position: -384 px -144 px ; } .icon-filter { background-position: -408 px -144 px ; } .icon-briefcase { background-position: -432 px -144 px ; } .icon-fullscreen { background-position: -456 px -144 px ; }
...and then I could use an excel spreadsheet to do all the calculations in one go, I setup an excel sheet to do any sprite modifications as long as the formatting above is used and we only need 3 variables to replicate this process -img path, width and height, I will update with exact formula in those cells if people request those details but for now here is the result(after a bit more clever replace all commands in notepad++ to remove spaces between integers and px and adding some carriage returns)...
i.icon-glass::after{ clip: rect( 0px 14px 14px 0px)!important; margin-top: 0px!important; margin-left: 0px!important; content: url('../img/glyphicons-halflings.png')!important; position:absolute!important; width:auto!important; height:auto!important; } i.icon-music::after{ clip: rect( 0px 38px 14px 24px)!important; margin-top: 0px!important; margin-left: -24px!important; content: url('../img/glyphicons-halflings.png')!important; position:absolute!important; width:auto!important; height:auto!important; } i.icon-search::after{ clip: rect( 0px 62px 14px 48px)!important; margin-top: 0px!important; margin-left: -48px!important; content: url('../img/glyphicons-halflings.png')!important; position:absolute!important; width:auto!important; height:auto!important; }
Arg I ran out of character space and hyperlinks since my reputation is too low which you can help me with) I posted the entire CSS result in the Bootstrap Issue Report referenced in an earlier answer https://github.com/twitter/bootstrap/issues/4412
WHEN IT WONT WORK
Now anyone that has tested this by viewing in their browser window using their print stylesheet instead of screen will see that it works perfectly and as I said earlier this solution as far as I can tell will work except in 10%-20% percent of cases. The exceptions to this solution will only show up when actually printing the pages(or printing to a file for paperless debugging).
What happens is the new foreground images sprites can overflow outside printable areas because of the position: absolute;
property which is required to use clip
property. When it comes to the W3C standard the rendering of these images is undefined as stated in the CSS Paged Media Module Level 3 in section 4.2-Content outside the page box;
This specification does not define how boxes positioned outside the page box are handled.
(Also check this for an older but better explanation HTML print with absolute postitions )
So what do the browser giants do when no standard has been agreed upon, they all do something different. What happens is the entire sprite image(non-visible portion) that overflows along the top, bottom and sides of the printable page area forces the browsers to decide how to handle and reconcile the CSS and printable page areas. This browser correction is not visible when viewing the in the browser because it is all one page and images can overflow side limits without issue. I'll explain what each one does as of May 28 2014 and most likely why it is happening this way.
First lets go with the browser that handles it the best,
Internet Explorer!
(I bet you though I was going to say anything else) The image is clipped properly but is pushed away from limiting printable area edges and so will appear in the wrong place on print out.
Safari and Chrome behave similarly, the image is moved by limiting printable area edges but the clip remains in the spot is was designated so wrong or no icon is shown.
Firefox appears to handle this the worst by only printing icons on the first page and if overflow occurs then forces all remaining icons into the top page on top of one another within the div or section it exists. (one might argue that this precludes Firefox from the overall solution but the fact that the first page works makes me hopeful that Mozilla will resolve in the future if we ask nicely)
Why do I say this will work for 80-90%? because the size of the sprite and the distance from printable area are 2 determining factors that will vary widely from page to page and should only effect in most cases up to 20% of the printable area.
SOLUTION FOR THESE 10-20% OF OCCURRENCES
In my case the icon is being used in a large list across many pages and so the globe-icon
at the top of almost every page is misaligned or the wrong icon depending on which browser.
Since I know this page will be printed often and needs to be accurate then I need to make sure this works at least 99% of the time. I'll do this by cutting out the globe-icon from the sprite and and insert it without all the extra positioning and clipping CSS (which is the original best answer for this issue).
i.icon-globe::after{ content: url('../img/globe-glyphicon.png')!important; }
and what about that 1% of users that still cant print this off properly, I print to a PDF file from a browser that does work and I make that available to download and print.
Thx for reading (@_@)
From Bootstrap's mdo: "It's a background image and they are likely being removed by the browser when printing."
https://github.com/twitter/bootstrap/issues/4412
I was stumped on this one for a good bit too. I ended up making a dedicated image of the glyph I was using instead of using the glyphicon system. Then I used @print
and :content
to inject the image wherever the icon should be.
@media print {
i.glyphicon-arrow-right{ content: url(../img/arrow.png) !important;}
}