skip to Main Content

TL/DR: where can I download the woff/woff2 variable font file from Google so we can host it on our server.

I am trying to update several google fonts from older versions to variable font files.

Our client wants us to host them on the site, as when we used the google import css property it adds Google cookies which we want to bypass.

In our current repo we have the following (truncated):

source-sans-pro-v21-latin-600.woff            
source-sans-pro-v21-latin-600.woff2           
source-sans-pro-v21-latin-regular.woff        
source-sans-pro-v21-latin-regular.woff2       
source-serif-pro-v15-latin-600italic.woff     
source-serif-pro-v15-latin-600italic.woff2    
source-serif-pro-v15-latin-700italic.woff     
source-serif-pro-v15-latin-700italic.woff2    
source-serif-pro-v15-latin-italic.woff        
source-serif-pro-v15-latin-italic.woff2

When going to the new Source family https://fonts.google.com/specimen/Source+Sans+3/about?query=Paul+D.+Hunt If I download the files I get ttf (for working on desktop).

I don’t actually know where I can download the new woff2 file from if we want to serve it.

If I inspect the page I can see the Source woff2 in the inspector. Is this the right way to go about this? I want to make sure I am on the right track. But manually downloading woff files via the inspector does not seem like the right way to do this (there are 5 font families in total in addition to Source, Inter, Montserrat etc).

enter image description here

This issue came about as the client noticed problems with Czech and Slovak characters for the existing font (I believe we require latin-ext for this) but we will also have Greek and Israeli alphabets to deal with. I assume the variable font handles this. It seems to look much better in the font tester at https://fonts.google.com/specimen/Source+Sans+3/tester?query=Paul+D.+Hunt

I also checked the github repo which is quite confusing:

Can anyone tell me the right way to go about this?

2

Answers


  1. The folder in the github repo you referenced, https://github.com/adobe-fonts/source-sans/tree/release/WOFF2/VF, has the WOFF2 files you’re looking for.

    There are two with ".otf" in the filename, and two with ".ttf" in the file name. The extension ".otf" is indicating that those are implemented using CFF or CFF2 format (a derivative of Postscript) for glypyh outlines. The ".ttf" extension indicates those are implemented using TrueType outlines. For use on the Web, either would be fine; you don’t need both.

    "-Upright" and "-Italic" in the filenames should be self-explanatory.

    This issue came about as the client noticed problems with Czech and Slovak characters for the existing font (I believe we require latin-ext for this) but we will also have Greek and Israeli alphabets to deal with. I assume the variable font handles this.

    Variable capabilities in a font are completely independent of what characters are supported in the font. You can see information about the characters supported here: https://fonts.google.com/specimen/Source+Sans+3/glyphs?query=Paul+D.+Hunt. It appears that the font does support Greek, as well as Czech and Slovak, but not Hebrew. You’ll have to find a different font for that.

    Login or Signup to reply.
  2. As explained by Peter Constable:

    Switching to variable fonts won’t solve issues regarding missing or non-ideal language support like as for Hebrew.

    Fetch google fonts for GDPR compliant font delivery

    To facilitate the downloading process
    you may also build your custom google variable font helper to get a "fontkit" similar to google webfont helper (which to this date unfortunately lacks support for variable fonts).

    // inputs
    const inputUrl = document.getElementById('inputUrl');
    const btnDownload = document.getElementById('btnDownload');
    
    // init example
    (async() => {
      updateGoogleCssUrl();
    })();
    
    inputUrl.addEventListener("input", async(e) => {
      updateGoogleCssUrl()
    });
    
    // fetch 
    async function updateGoogleCssUrl() {
      // fetch css content as text
      let url = inputUrl.value;
      let css = await (await fetch(url)).text();
      btnDownload.classList.replace('active', 'inactive');
      btnDownload.classList.add('loading');
    
      // fetch font files and zip
      let blob = await fetchFontsFromCssAndZip(css);
      let objectUrl = URL.createObjectURL(blob);
    
      // update download link
      btnDownload.href = objectUrl;
      btnDownload.download = blob.name;
      btnDownload.classList.replace('inactive', 'active');
      btnDownload.classList.remove('loading');
    
    }
    
    
    async function fetchFontsFromCssAndZip(css) {
    
      // find subset identifiers by comments
      let regexComments = //*s*([^*]*(?:*(?!/)[^*]*)*)*//g;
      let subsets = css.match(regexComments).map(sub => {
        return sub.replace(/(/*|*/)/g, '').trim()
      });
    
    
      //create and parse temporary stylesheet object
      let cssSheet = new CSSStyleSheet()
      cssSheet.replaceSync(css)
    
      // filter font-face rules
      let rules = [...cssSheet.cssRules].filter(rule => {
        return rule.type === 5
      })
    
      // sanitize font-family name 
      let fontFamily = rules[0].style.getPropertyValue('font-family').replaceAll('"', '');
      let fontFamilyFilename = fontFamily.replaceAll(' ', '-');
    
      // create zip object
      let zip = new JSZip();
    
      // loop through all rules/fonts
      for (let i = 0; i < rules.length; i++) {
    
        // get properties
        let fontWeight = rules[i].style.getPropertyValue('font-weight')
        let fontStyle = rules[i].style.getPropertyValue('font-style')
        let fontStretch = rules[i].style.getPropertyValue('font-stretch')
        fontStretch = fontStretch === 'normal' ? '' : fontStretch;
        let src = rules[i].style.getPropertyValue('src')
        src = src.match(/(([^)]+))/)[1].replaceAll('"', "");
    
        //replace cryptic file names with readable local names
        let fontName = [fontFamilyFilename, subsets[i], fontWeight, fontStyle, fontStretch].filter(Boolean).join('_') + '.woff2'
        css = css.replaceAll(src, `"${fontFamilyFilename}/${fontName}"`)
    
        // add data to zip
        let fontData = await (await fetch(src)).arrayBuffer();
        zip.file(`${fontFamilyFilename}/${fontName}`, fontData, {
          type: "uint8array"
        });
    
      }
    
      // add simple example HTML
      let htmlDoc = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
      <link rel="stylesheet" href="${fontFamilyFilename}.css">
      <body style="font-family:'${fontFamily}'">
      <h1>Sample font</h1>
      <p>One morning, when <em>Gregor Samsa</em> woke from <strong>troubled dreams</strong>, he found himself transformed in his bed into a horrible vermin.</p>
      <p>He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover it and seemed ready to slide off any moment.</p>
      </body></html>`;
      zip.file('index.html', htmlDoc);
    
    
      // add css
      fontCss.value = css;
      zip.file(`${fontFamilyFilename}.css`, css);
    
      // create object url
      let blob = await zip.generateAsync({
        type: "blob"
      });
    
      blob.name = fontFamilyFilename + '.zip';
      return blob;
    }
    :root {
      --loadingImg: url("data:image/svg+xml,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><path d='M12 1A11 11 0 1 0 23 12 11 11 0 0 0 12 1Zm0 19a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z' opacity='.25'/><path d='M10.14 1.16a11 11 0 0 0-9 8.92A1.59 1.59 0 0 0 2.46 12 1.52 1.52 0 0 0 4.11 10.7a8 8 0 0 1 6.66-6.61A1.42 1.42 0 0 0 12 2.69h0A1.57 1.57 0 0 0 10.14 1.16Z'><animateTransform attributeName='transform' type='rotate' dur='0.75s' values='0 12 12;360 12 12' repeatCount='indefinite'/></path></svg>")
    }
    
    body {
      font-family: sans-serif
    }
    
    legend {
      font-weight: bold;
    }
    
    fieldset {
      margin-bottom: 1em;
    }
    
    fieldset input,
    fieldset textarea {
      border: none
    }
    
    input {
      width: 100%;
      display: block;
      margin-bottom: 1em;
    }
    
    textarea {
      width: 100%;
      min-height: 20em;
    }
    
    .btn-default {
      text-decoration: none;
      border: 1px solid #000;
      background: #ccc;
      color: #000;
      font-weight: bold;
      padding: 0.3em;
    }
    
    .inactive {
      pointer-events: none;
      opacity: 0.3;
    }
    
    .btn-load .icn-loading {
      opacity: 0;
    }
    
    .btn-load.loading .icn-loading {
      opacity: 1;
    }
    
    .btn-load.active .icn-loading {
      width: 0px;
    }
    
    .icn-loading {
      transition: 0.3s;
      transform: translateY(0.15em);
      display: inline-block;
      position: relative;
      overflow: hidden;
      width: 1em;
      height: 1em;
      background-image: var(--loadingImg);
      background-repeat: no-repeat;
      background-position: 0%;
      color: transparent;
      border-color: transparent;
    }
    <h1>Fetch variable fonts from google</h1>
    <p><a class="btn-default btn-load inactive" id="btnDownload" href="#" download="fontface.css">Download fontkit <span class="icn-loading"></span></a> </p>
    <fieldset>
      <legend>Enter CSS Url</legend>
      <input type="text" id="inputUrl" value="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900">
    </fieldset>
    
    <fieldset>
      <legend>New Css</legend>
      <textarea id="fontCss"></textarea>
    </fieldset>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.js"></script>

    Download links don’t work in SO snippets.
    See fully working codepen example.

    How it works

    1. fetch the remote CSS queries to retrieve all @font-face rules
    2. parse the CSS to get src properties etc.
    3. fetch all referenced font files
    4. create a zip file including all woff2 files (I’m using jszip.js library – but you can use any other library as well)
    5. replace font file names and paths in a newly created CSS file

    User agent detection

    Google font API still leverages browser sniffing:
    When retrieving the CSS in Opera (also blink based like chrome, brave, edge etc) it is still categorized as a browser that doesn’t support variable fonts (albeit Opera seems to be an exception).

    Download font-family vs. fetching

    When retrieving google fonts via API font-families are split into subsets for different languages.

    Quite often you don’t need the full range – you may have a site based language toggle concept e.g switching between English and lets say Greek. So you don’t necessarily need to load the most complete font data but rather subsets for the currently selected languages on demand.

    In the above example we’re using the same concept as google:
    We only load font files if additional language subsets are required.

    When you’re downloading font files via font-family download button you get a font including all available unicode ranges – resulting in a larger filesize.

    Variable fonts vs. "static fonts"

    Variable fonts are great if you need fine grained control for multiple font-weights, widths etc.

    But they are definitely not a "silver bullet" solution to significantly improve loading performance – you need to test and compare the differences to find the best option for your application.

    If you only need a few weights and styles – the old-school static webfonts files may provide a smaller download size and maybe a better conceived rendering performance.

    Up-to-date font versions

    Versions that could be found on github are more up-to-date. If you find issues in the google hosted font files you can compare the differences with the most recent dev versions hosted on github.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search