Death to typewriters: Technical supplement
Non-breakable spaces
We define what punctuation characters should be followed by a non-breakable space, and what should be preceded by one:
var NBSP = goog.string.Unicode.NBSP
_.NBSP_PUNCTUATION_START = /([«¿¡]) /g
_.NBSP_PUNCTUATION_END = / ([\!\?:;\.,‽»])/g
And then when we render paragraphs, we decide which plain spaces turn into non-breakable spaces with this:
text = text.replace(_.NBSP_PUNCTUATION_START, '$1' + NBSP)
.replace(_.NBSP_PUNCTUATION_END, NBSP + '$1')
The side effect of this method is that we can upgrade our rendering without having to change the underlying text.
❦
II. Making type read well and look good
Font smoothing
As far as I understand, this is only necessary for Macs:
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
Line height and tracking
Relatively straightforward:
line-height: 1.5;
letter-spacing: 0.01rem;
Kerning and ligatures
We had a few little issues with using optimizeLegibility, but we are using it overall:
text-rendering: optimizeLegibility;
Firefox needed to turn the ligatures on independently:
-moz-font-feature-settings: “liga” on;
In some places where optimizeLegibility is kicking our ass (things unnecessarily breaking to a new line even though there is room), we just override it back:
text-rendering: auto;
Using different fonts for specific glyphs
Single one so far, for ██████ on Mac OS (but see pilcrows below).
@font-face {
font-family: 'Cambria';
src: local('Arial'), local('Helvetica');
Printing
We are hiding everything not in the main content area, so that it doesn’t print by default. In many cases we will still have to use display: none to hide individual screen-only UI objects, since the below alone still reserves the space… but this is still better than any new UI elements “leaking” into print view if we don’t pay attention.
@media print {
body.template-flex-article * {
visibility: hidden;
}
body.template-flex-article .postContent,
body.template-flex-article .postContent * {
visibility: visible;
}
}
We are setting up top and bottom margins:
@media print {
@page {
margin-top: .75in;
margin-bottom: .75in;
}
}
We’re changing the width of the page to match the different font size:
@media print {
body.template-flex-article .layoutSingleColumn {
max-width: 4.95in;
margin: 0 auto;
}
}
We’re overriding the font colour to be black, plus ensure that orphans and widows don’t exist (2 below means we don’t display one line by itself if it begins or ends the page). Alas, Firefox and Safari don’t support this yet.
@media print {
body {
color: black;
orphans: 2;
widows: 2;
}
}
We’re hiding both default underlines and our custom ones:
@media print {
.markup—anchor,
.markup—pre-anchor {
text-decoration: none;
background: none;
}
}
We’re hiding actionable elements in the footer, and make sure it travels to a new page together, rather than being cut in the middle:
@media print {
.postFooter--simple {
page-break-inside: avoid;
}
.postFooter-actions--simple,
.infoCard-actions {
display: none;
}
}
❦
III. Punctuation binds the words together
Bullet points and ordered lists
This is the style we use for lists:
.postList > li:before {
position: absolute;
display: inline-block;
box-sizing: border-box;
// The list gutter content width needs to be the image
// gutter, or the list will bleed back into a floating
// image. We align the text right so that large numbers
// can bleed out further.
width: 58px;
margin-left: -58px;
text-align: right;
}
ol.postList > li:before {
padding-right: 12px;
counter-increment: post;
content: counter(post) ".";
}
And here are our custom bullet points:
ul.postList > li:before {
padding-top: 6px;
padding-right: 15px;
font-size: @fontSize-base--post * 0.65;
content: '•';
}
Hyphenation
We only apply it to articles with a specified language, to avoid drive-by hyphenation:
.postArticle[lang] .graf--p,
.postArticle[lang] .graf--blockquote {
-webkit-hyphens: auto;
-webkit-hyphenate-limit-before: 2;
-webkit-hyphenate-limit-after: 3;
-webkit-hyphenate-limit-lines: 2;
}
Then we specify the proper language for the article:
<article class='postArticle' lang='en'>
Underlines
// Position of the underline for a certain font
@backgroundPosition—underlineSerif: 0.72;
@backgroundPosition—underlineSansSerif: 0.90;
// How thick the underlines are as multiplied by font size
@width—underlineRatio: 0.1;
// Underline mixin
.m-underlinePosition(@font-size, @line-height, @m-underlinePosition) {
background-position: 0 ceil(@font-size * @line-height * @m-underlinePosition);
}
The modifier ceil was picked specifically so our underlines looked good under certain circumstances (your mileage may vary).
// Underline under regular text
.tier-1 .markup--anchor {
text-decoration: none;
background-image: linear-gradient(to bottom, @color-transparentBlack 50%, @color-transparentBlackDark 50%);
background-repeat: repeat-x;
background-size: 2px floor(@fontSize-base--post * @width--underlineRatio);
.m-underlinePosition(@fontSize-base--post, @lineHeight-base--post, @backgroundPosition--underlineSerif);
}
// Underline under an H1
.tier-1 .markup--h2-anchor {
background-image: linear-gradient(to bottom, @color-transparentBlackDarker, @color-transparentBlackDarker);
background-repeat: repeat-x;
background-size: 2px floor(@fontSize-larger * @width--underlineRatio);
.m-underlinePosition(@fontSize-jumbo--post, @lineHeight-tight, @backgroundPosition--underlineSansSerif);
}
We specify overrides for retina displays to have sharp 1-pixel underlines:
// Retina override
@media only screen and (min-device-pixel-ratio: 2),
only screen and (min-resolution: 2dppx),
only screen and (-webkit-min-device-pixel-ratio: 2) {
.tier-1 .markup--anchor {
background-image: linear-gradient(to bottom, @color-transparentBlack 75%, @color-transparentBlackDarker 75%);
background-repeat: repeat-x;
}
}
Possible improvement is to specify positions in ems (which we avoid in our codebase), so that browsers with minimal font size would still have proper underlines.
We only do custom underlines in the body copy, not in the UI — they are pretty expensive to maintain.
Pilcrows
Pretty straightforward styling of the pilcrow itself:
.pilcrow {
font-family: "Arial", sans-serif;
font-size: .7em;
padding: 0 .25em;
position: relative;
top: -.15em;
opacity: .4;
}
And we wrap it around where it appears:
text = text.replace(/¶/g, '<span class=”pilcrow”>¶</span>')
❦
IV. Typography is more than just letters
Old-style numerals
We are lucky enough that the fonts we use come with proper defaults. Freight Text Pro (serif font you’re reading now) has default old-style numerals built in. Bernino Sans (sans serif headline font we use) has lining numerals.
Tabular figures
There is a CSS property (tabular-nums) living under font-variant. We cannot use it since the font we’re serving doesn’t support this OpenType property yet.
We fake it by wrapping each individual digit and comma with specially styled wrappers:
.tabularNumeral {
display: inline-block;
width: .56em;
text-align: center;
}
.tabularNumeral—comma {
width: .35em;
text-align: left;
}
And this is how we separate them:
shared.soy.tabularNumeral = function (numString) {
var tabularString = ''
for (var i = 0; i < numString.length; i++) {
var className = 'tabularNumeral'
if (numString[i] == ',') {
className += ' tabularNumeral--comma'
}
tabularString += '<span class="' +
goog.string.htmlEscape(className) + '">' +
numString[i] + '</span>'
}
return soydata.VERY_UNSAFE.ordainSanitizedHtml(tabularString)
}
Side note: This is what happened when I screwed up the above function:
Unicode is fun!
Thousand separators
This is a quick function that puts a comma every third number:
shared.soy.thousandSeparator = function (numString) {
return numString.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
(You might notice a lot of these typographical enhancements are implemented as Closure templates compiler plugins, which do a lot to protect us from XSS security holes.)
❦
V. Whitespace is as important as content
Multiple spaces
Multiple spaces is less important in HTML (browsers de-dup them), but we still need to remove them for when we render on other platforms, for example iOS.
Vertical spaces
At writing, we apply special class .graf--empty to a paragraph without any contents. Then we tighten things up with negative margins:
.graf--h2 + .graf--p.graf--empty,
.graf--h3 + .graf--p.graf--empty,
.graf--h4 + .graf--p.graf--empty {
margin-bottom: -7px;
margin-top: -7px;
}
.graf--h2 + .graf--p.graf--empty + .graf--h2,
.graf--h3 + .graf--p.graf--empty + .graf--h2,
.graf--h4 + .graf--p.graf--empty + .graf--h2 {
margin-top: -5px;
}
Side note: why graf and not paragraph? Read on…
Headline alignment
Very simple. Could conceivably be better expressed with ems as units.
.m-fontSizeWithLeftAlignmentFix(@size) {
font-size: @size;
margin-left: [email protected] / 20;
}
❦
I personally implemented only a fraction of the above. Thank you to our tireless engineers who spend time caring about words and typography. In no particular order: Nick Santos (with extra thanks for reviewing this article), Daryl “Koop” Koopersmith, Jacob “Fat” Thornton, Kyle Hardgrave, and Gianni Chen.