Last Updated: February 25, 2016
·
1.679K
· mturnwall

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.