# Owner: glow # KeyID: E65A8520F36AEE13CFE4F56BEB8FE331063A4794 # Key: https://glow.li/pgp # Verify: curl "https://glow.li/posts/using-pixel-fonts-in-a-browser-without-font-smoothing/index.html.asc" | gpg -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 +++ { "css": ["pixel-zoom.css", "special-image-tinier.css"], "date": "2016-07-15", "layout": "post", "oldurls": [ "technology/2016/7/15/using-pixel-fonts-in-a-browser-without-font-smoothing/" ], "tags": [ "Technology", "Web_Development", "Fonts", "Showcase", "CSS", "HTML", "Jekyll", "Ruby" ], "title": "Pixel fonts in a browser without font smoothing" } +++ [Tinier](https://squaregear.net/fonts/tinier.html) is a pretty cool font. It is a so called pixel font, and it is just about the smallest font I could find. The letters can be displayed at a font-size of `4px`. This means that each character is only 3 pixels tall.
I think this is pretty cool, so I tried to implement this on my website for the footer blurb. It turns out though, that pixel fonts look absolutely hideous in modern browsers. Here is how it looked: ![Font sample](media/images/tinier-sample-white) ![Font sample](media/images/tinier-sample-white){.pixel-zoom} Do you see these hideous colored pixels? It ruins the pixel font. The footer was originally white on blue, where the problem was even worse: ![Font sample](media/images/tinier-sample-blue) ![Font sample](media/images/tinier-sample-blue){.pixel-zoom} How it should look: ![Font sample](media/images/tinier-sample-sprite) ![Font sample](media/images/tinier-sample-sprite){.pixel-zoom} Nice, clean, black on white Pixels. As with any modern software, browser employ font smoothing. This is done so text looks good on a low DPI screen. It is done with anti aliasing and sub pixel rendering and for normal fonts this is pretty great, but for pixel fonts it looks terrible. There is no way for a font to disable it, so it usually has to be done at a software level. Some browsers have a setting for disabling font smoothing. This is obviously pretty useless, since I can't expect readers of my blog to have turned this off and made all normal fonts look bad except my tiny blurb. ## There must be a CSS setting right? Well, let me tell you about `font-smooth`. It was present in early drafts of CSS3, but has been removed from the specification since. It only works on [some browsers](https://caniuse.com/font-smooth) and only on the Mac OSX operating system. So it looks like there is no easy way to implement a pixel font without ugly rendering artifacts. ## But why don't you just use an image? Yes, I could just write my text in GIMP and upload it as a picture. But I want it to be dynamically generatable. I want to do it in good old HTML and CSS. ## So I have decided to go the sprite map route instead ![Font sample](media/images/tinier){.special-image-tinier} Having it as a sprite map has many advantages including (and probably limited to): - - You can still wrap text around newlines - - You only have to have one image for any size and length of text. - - Because the original characters are still in the HTML you can select and copy it Here is how the HTML of a very short text looks: ``` ``` As you can see every letter is just a `` with a special class and the original letter inside it. And since this is a [Jekyll](https://jekyllrb.com) blog, I wrote a short ruby plugin to achieve this, but it would probably work really well in JavaScript as well. ``` module Jekyll class Charfont < Liquid::Tag def initialize(tag_name, text, tokens) super html = "" text.upcase.split("").each do |i| if i == " " classname="SP" elsif i == "." classname="dot" else classname=i end html = html + "" + i + "" end html = html + "" @output=html end def render(context) @output end end end Liquid::Template.register_tag('charfont', Jekyll::Charfont) ``` And here is the accompanying CSS: ``` .charfont-tinier span { color: transparent; background-image: url("../media/images/tinier"); margin-left: calc(1px * var(--scale)); display: inline-block; background-size: calc(5px * var(--scale)); width: calc(3px * var(--scale)); height: calc(4px * var(--scale)); overflow: hidden; background-repeat: no-repeat; image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: pixelated; image-rendering: optimize-contrast; -ms-interpolation-mode: nearest-neighbor } .charfont-tinier{ - --scale:1; } .charfont-big .charfont-tinier{ - --scale:5; } .charfont-tinier .charfontA { background-position: 0px calc(-0px * var(--scale)); } .charfont-tinier .charfontB { background-position: 0px calc(-4px * var(--scale)); } .charfont-tinier .charfontC { background-position: 0px calc(-8px * var(--scale)); } ``` You can look at the whole CSS file [here](css/charfont.css). So, this is a very complicated solution to a problem that really should be as simple as `font-smooth`. And it still isn't prefect. Having it do proper line-wrapping would require a bit more complicated HTML and CSS. Also the `image-rendering` part doesn't work on all browsers, so when text size is large it would look like this: ![Font sample](media/images/tinier-sample-sprite){.zoom} Edit: Since writing this post Chrome has made some changes. It now enforces a minimum `font-size` of 6px. Therefore my previous method of using `em` and `font-size=1px` no longer works. I've updated the code in this post to use a css variable to define scale. This way I can still enlarge the font with CSS. -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEE5lqFIPNq7hPP5PVr64/jMQY6R5QFAmYUK9gACgkQ64/jMQY6 R5QwoRAAoKTO2UQo1dSah+IDx3qQs5iK1VTB+iseavGrIF3Tqq1V033JKz/SiPbS Hy7em4jY9Xgu6FSR1S0DgC66AeviIKGcJXnrdLNbRpIK5d4D0RVABCHxPoT4H1Fn UEs0LMfkxn4kIDToTpr2W3YWMbrXNUjhCHWROiTCtCvVSL3wCZuprLwYvjvJEUtl q+oWADKs1jURgH132EL+7m4yB9jp0ZLxhXjL5EifI4cgPhB5/HA1Rw8l+kSQSZO2 QyPjCFtoCPgXNEvHwPXH/aTTiej+DUnVAfMuxxM8sBjyyzdrwOsTkV27eyBs1yVn TBsSeCFr62CO+fWn0SV4WHvLdwBHxjcRAerQbmAQMU+0Asl0CXfEcBhre2DIW0PE RDAq0fpIW+Bvzb7mcqO6/lhgFE5cCl1bHtE6xOIJzfheef3JnzEimBErsuZjkiws 0QphDhNcDO6snYx4yMN7bTl1M8z2czHBsaBNpnnv/gV++fJ2CIPaM/ureBa7O6Y6 QBJAC/hecWs1k/jqsVqVFRwtzfK8H0zgv0zIYTkVmcjOusDxYJubp1FdWopA9K79 4/MHo+5+/x3rSHZkfBo52E9pcPikWKt7KvzQRSZsAHxu6qafziLIG3jc2z00Qmv/ g3Zm5JlWD9W+1mfWTG3SjfBzIg4pC8DiGSS1UX9lMCG57C7uFig= =aCff -----END PGP SIGNATURE-----