List all variable fonts from Google Fonts API?

971 views Asked by At

I need to get a list of all variable fonts available through the Google Fonts API.

I can get all the font names from this endpoint. There are some parameters you can add but I don't think that a variable font filter is among them: https://www.googleapis.com/webfonts/v1/webfonts?key=API_KEY&sort=popularity

I don't think I can filter the results after making the API call. Here "Open Sans" is a variable font but I don't see anything to indicate this in the response.

{
      "family": "Open Sans",
      "variants": [
        "300",
        "regular",
        "500",
        "600",
        "700",
        "800",
        "300italic",
        "italic",
        "500italic",
        "600italic",
        "700italic",
        "800italic"
      ],
      "subsets": [
        "cyrillic",
        "cyrillic-ext",
        "greek",
        "greek-ext",
        "hebrew",
        "latin",
        "latin-ext",
        "vietnamese"
      ],
      "version": "v34",
      "lastModified": "2022-09-22",
      "files": {
        "300": "http://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4nY1M2xLER.ttf",
        "regular": "http://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4nY1M2xLER.ttf",
        "500": "http://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4nY1M2xLER.ttf",
        "600": "http://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4nY1M2xLER.ttf",
        "700": "http://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4nY1M2xLER.ttf",
        "800": "http://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4nY1M2xLER.ttf",
        "300italic": "http://fonts.gstatic.com/s/opensans/v34/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVcUwaERZjA.ttf",
        "italic": "http://fonts.gstatic.com/s/opensans/v34/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVcUwaERZjA.ttf",
        "500italic": "http://fonts.gstatic.com/s/opensans/v34/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVcUwaERZjA.ttf",
        "600italic": "http://fonts.gstatic.com/s/opensans/v34/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVcUwaERZjA.ttf",
        "700italic": "http://fonts.gstatic.com/s/opensans/v34/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVcUwaERZjA.ttf",
        "800italic": "http://fonts.gstatic.com/s/opensans/v34/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVcUwaERZjA.ttf"
      },
      "category": "sans-serif",
      "kind": "webfonts#webfont"
    },
2

There are 2 answers

2
zomega On BEST ANSWER

There is this site which lists all variable fonts. In the developer console (F12) I saw it downloading this file:

https://fonts.google.com/metadata/fonts

This file contains all fonts. If the "axes" array of a font is not empty then it's definitely a variable font. Maybe you have to check other property names (I am not that familiar with variable fonts).

0
herrstrietzel On

Open API: add capability parameter "VF"

If you're using the open developer API, you also need to set the parameter capability=VF to retrieve axes data:

https://www.googleapis.com/webfonts/v1/webfonts?capability=VF&sort=style&key=${apiKey}

Other capability options:

  • WOFF2: returns woff2 font file URLs
  • CAPABILITY_UNSPECIFIED: returns ttf/truetype font file URLs

You can combine these options to get either woff2 variable font URLs or truetype:

https://www.googleapis.com/webfonts/v1/webfonts?capability=VF&capability=WOFF2&sort=style&key=${apiKey}

However, this won't filter the list to variable fonts only. So you need to filter the items array manually.

(async ()=>{
// fetch font JSON
let listObj = await (await fetch(dataAPIStatic)).json();
// filter items with axes properties
let itemsVariable = listObj.items.filter(item => item.axes && item.axes.length);
output.value= JSON.stringify(itemsVariable, null, ' ');
})()

/**
 * static copy of
 * https://www.googleapis.com/webfonts/v1/webfonts?capability=VF&capability=CAPABILITY_UNSPECIFIED&sort=style&key=APIKEY
 */
let dataAPIStatic = "https://cdn.jsdelivr.net/gh/herrstrietzel/fonthelpers@main/json/gfontsAPI.json";


(async() => {
  // fetch font JSON
  let listObj = await (await fetch(dataAPIStatic)).json();
  // filter items with axes properties
  let itemsVariable = listObj.items.filter(item => item.axes && item.axes.length);
  output.value = JSON.stringify(itemsVariable, null, ' ');

})()
textarea {
  width: 100%;
  min-height: 75vh;
}
<textarea id="output"></textarea>

Retrieve data from meta JSON

If you don't have an API key you can also get the required data from the aforementioned meta URL https://fonts.google.com/metadata/fonts.

But you can't retrieve the data directly via javaScript fetch as the URL doesn't allow cross origin access.

As a workaround you can host your own copy locally or on a cdn.
Obviously, this JSON won't update automatically, so you need to update the file once in a while.

The meta JSON also has a different structure.
E.g. axes min and max values are stored like this:

Meta

{tag: 'wdth', min: 100.0, max: 200.0, defaultValue: 100.0}

API

{tag: 'wdth', start: 100, end: 200}

So you need to adapt your filtering script.

Example: load meta or API data helper

let apiKey = "your APi key";
let dataAPIStatic =
  "https://cdn.jsdelivr.net/gh/herrstrietzel/fonthelpers@main/json/gfontsAPI.json";
let dataMetaStatic =
  "https://cdn.jsdelivr.net/gh/herrstrietzel/fonthelpers@main/json/gfontsMeta.json";

// set query options
let options = {
  //apiKey: apiKey,
  format: "woff2",
  variableFonts: true,
  staticURL: dataMetaStatic
};

// init
(async () => {
  let googleFontList = await getGoogleFontList(options);

  // generate autofill
  populateDatalist(datalistFonts, googleFontList);
  let itemDefault = googleFontList.filter(
    (item) => item.family === "Open Sans"
  );

  // get URLs
  let googleFontUrls = await getGoogleFontUrls(itemDefault);
  //console.log(googleFontUrls)
  showLinkList(cssUrls, googleFontUrls);

  // filter fonts
  inputFonts.addEventListener("change", async (e) => {
    let family = e.currentTarget.value;
    let familyQuery = family.replaceAll(" ", "+");
    let item = googleFontList.filter((item) => item.family === family);

    // update links
    googleFontUrls = await getGoogleFontUrls(item);
    showLinkList(cssUrls, googleFontUrls);
  });
})();

async function getGoogleFontList(
  options = {
    format: "woff2",
    variableFonts: true,
    staticURL: ""
  }
) {
  let { apiKey, format, variableFonts, staticURL } = options;
  let capabilities = [];
  if (variableFonts) {
    capabilities.push("capability=VF");
  }
  if (format) {
    let fontFormat =
      format === "ttf" || format === "truetype"
        ? "CAPABILITY_UNSPECIFIED"
        : "WOFF2";
    capabilities.push(`capability=${fontFormat}`);
  }

  let capabilityQuery = capabilities.join("&");
  let apiURL = `https://www.googleapis.com/webfonts/v1/webfonts?${capabilityQuery}&sort=style&key=${apiKey}`;

  // switch between API and static src
  let useStatic = !apiKey ? true : false;
  let listUrl = useStatic ? staticURL : apiURL;

  // fetch font JSON
  let listObj = await (await fetch(listUrl)).json();

  /**
   * normalize meta formating
   * and API structure
   */
  let listTypeMeta = listObj.familyMetadataList ? true : false;
  let items = listTypeMeta ? listObj.familyMetadataList : listObj.items;

  // filter variable
  items = items.filter((item) => item.axes && item.axes.length);


  if (listTypeMeta) {
    for (let i = 0; i < items.length; i++) {
      let item = items[i];
      let axes = item.axes;
      for (let a = 0; a < axes.length; a++) {
        let axis = axes[a];
        item.axes[a] = {
          tag: axis.tag,
          start: axis.min,
          end: axis.max,
          defaultValue: axis.defaultValue
        };
      }
    }
  }

  return items;
}

async function getGoogleFontUrls(googleFontList) {
  let urls = [];
  let gfontBase = `https://fonts.googleapis.com/css2?family=`;

  googleFontList.forEach((font) => {
    //console.log(isVariable);
    let family = font.family;
    let familyQuery = family.replaceAll(" ", "+");

    let variants = font.variants ? font.variants : [];
    let stylesItalic = font.variants
      ? variants.filter((variant) => variant.includes("italic"))
      : Object.keys(font.fonts).filter((val) => val.includes("i"));
    let stylesRegular = font.variants
      ? variants.filter((variant) => !variant.includes("italic"))
      : Object.keys(font.fonts).filter((val) => !val.includes("i"));

    // sort axes alphabetically - case sensitive ([a-z],[A-Z])
    let axes = font.axes;
    axes = [
      axes.filter((item) => item.tag.toLowerCase() === item.tag),
      axes.filter((item) => item.tag.toUpperCase() === item.tag)
    ].flat();
    let ranges = axes.map((val) => {
      return val.start + ".." + val.end;
    });
    let tuples = [];

    //  italic and regular
    if (stylesItalic.length && stylesRegular.length) {
      tuples.push("ital");
      rangeArr = [];
      for (let i = 0; i < 2; i++) {
        rangeArr.push(`${i},${ranges.join(",")}`);
      }
    }
    // only italic
    else if (stylesItalic.length && !stylesRegular.length) {
      tuples.push("ital");
      rangeArr = [];
      rangeArr.push(`${1},${ranges.join(",")}`);
    }

    // only regular
    else {
      rangeArr = [];
      rangeArr.push(`${ranges.join(",")}`);
    }

    // add axes tags to tuples
    axes.map((val) => {
      return tuples.push(val.tag);
    });
    query = tuples.join(",") + "@" + rangeArr.join(";") + "&display=swap";

    let url = `${gfontBase}${familyQuery}:${query}`;
    urls.push(url);
  });

  return urls;
}

function showLinkList(target, urls) {
  target.innerHTML = "";
  let urlList = "";
  urls.forEach((url) => {
    urlList += `<li class="liUrl"><a href="${url}">${url}</li>`;
  });
  target.insertAdjacentHTML("beforeend", urlList);
}

function populateDatalist(target, list) {
  let fonts = list;
  let datalistOptions = "";
  fonts.forEach((font) => {
    let option = document.createElement("option");
    //only VF
    if (font.axes) {
      datalistOptions += `<option >${font.family}</option>`;
    }
  });
  target.insertAdjacentHTML("beforeend", datalistOptions);
}
textarea {
  width: 100%;
  min-height: 20em;
}
<h1>Get variable font CSS URLS</h1>

<p><input type="text" list="datalistFonts" id="inputFonts" placeholder="Search google font"></p>
<datalist id="datalistFonts">
    </datalist>

<h3>CSS Urls</h3>
<ol id="cssUrls">
</ol>