Font Face Generator
I recently converted a project from Ruby Sass and Compass to libsass. The Compass mixin font-face
was being used to output the @font-face
rules. Since I didn't want to have to write individual @font-face
rules for this project or future projects, I decided to see if I could come up with my own version of a font-face
mixin.
Here is what I came up with.
$ff-dir: '/fonts' !default;
@function ff-build-src($font) {
$src: ();
$name: map-get($font, name);
@each $format in map-get($font, formats) {
$src: append($src, url($ff-dir + "/" + $name + "." + unquote($format)) format(quote($format)), "comma");
}
@return $src;
}
@mixin ff-single($fontName, $src, $weight: normal, $style: normal) {
@at-root {
@font-face {
font-family: quote($fontName);
src: $src;
font-weight: unquote($weight);
font-style: unquote($style);
}
}
}
@mixin ff-batch($fonts) {
@each $font-group-name, $font-groups in $fonts {
@each $font in $font-groups {
// default to woff and ttf if formats is missing
@if map-get($font, formats) {
$src: ff-build-src($font);
} @else {
$src: ff-build-src(map-merge($font, ("formats": ("woff", "ttf"))));
}
$font: map-merge(map-remove($font, name, formats), ("src": $src));
@include ff-single($font-group-name, $font...);
}
}
}
You call ff-batch
to output more than one @font-face
rule. The mixin accepts a Sass map that contains all the information about the fonts you are using. If you need just one @font-face
rule then just use the ff-single
mixin.
I feel that using a Sass map to hold all the font information such as the name of the font and it's weight is much easier to maintain than many @font-face
rules.
Here is an example of what the map looks like.
$ff-map: (
"Proxima-Nova": ( // font-family name
(
"name": "Proxima-Nova-Light",
"formats": ("woff", "ttf"),
"weight": 300
), (
"name": "Proxima-Nova-LightItalic",
"weight": 300,
"style": "italic"
), (
"name": "Proxima-Nova-Regular"
), (
"name": "Proxima-Nova-Medium",
"weight": 500
), (
"name": "Proxima-Nova-Bold",
"weight": 700
)
)
);
The first key in the map, "Proxima-Nova", is the name of your font. This value can be whatever you want. It is used as the value for the font-family
property and allows you to take advantage of style linking. This way every style of the "Proxima-Nova" font such as bold and italic, all use the same font-family value.
The value for that font-family name is list of Sass maps. Each map entry is a style and corresponds to a @font-family
rule. So the example above will output 5 @font-face
rules. The only property that is required is the name
property. This name corresponds to the actual font file name. The default font formats are woff
and ttf
and for weight and style the defaults are normal. You can override these defaults by providing values for the keys, format, weight, and style. For formats you'll need to use a list of the formats you want to include. All the different formats of your font file do need to have the same name.
When it comes to formatting the map, you can either use quotes around the keys and values or not. The mixin will always output the correct format. Just make sure you are consistent. For me since the map format resembles JSON
I just follow the format rules for JSON
.
Usage
So using the Sass map above you call the mixin like so.
@include ff-batch($ff-map);
And your compiled CSS comes out like this.
@font-face {
font-family: "Proxima-Nova";
src: url("/fonts/Proxima-Nova-Light.woff") format("woff"), url("/fonts/Proxima-Nova-Light.ttf") format("ttf");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "Proxima-Nova";
src: url("/fonts/Proxima-Nova-LightItalic.woff") format("woff"), url("/fonts/Proxima-Nova-LightItalic.ttf") format("ttf");
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: "Proxima-Nova";
src: url("/fonts/Proxima-Nova-Regular.woff") format("woff"), url("/fonts/Proxima-Nova-Regular.ttf") format("ttf");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Proxima-Nova";
src: url("/fonts/Proxima-Nova-Medium.woff") format("woff"), url("/fonts/Proxima-Nova-Medium.ttf") format("ttf");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "Proxima-Nova";
src: url("/fonts/Proxima-Nova-Bold.woff") format("woff"), url("/fonts/Proxima-Nova-Bold.ttf") format("ttf");
font-weight: 700;
font-style: normal;
}
You can call this mixin anywhere in your Sass because the @font-face
rules will get outputted at the root level of your CSS.
If you feel the urge, you can also call ff-single
to output a single @font-face
rule. Although ff-batch
will do the same thing if your map contains a single entry.
$icomoon-list:
url("/fonts/icomoon.woff") format("woff"),
url("/fonts/icomoon.ttf") format("ttf"),
url("/fonts/icomoon.svg") format("svg");
// The 1st argument is the font-family and the second is a list of the font-file URLs.
// Weight and style are optional 3rd and 4th arguments respectively.
@include ff-single('icomoon', $icomoon-list);
By default the fonts directory is located at /fonts
. You can override this by setting a variable called $ff-dir
. Just set the value of that variable to the location of your font files.
Big thanks to Hugo Giraudel for giving me some great advice about this.
You can try out the documented mixin on SassMeister.
There is also a gist on github.