How does the Google Fonts CSS v2 API work exactly, in the case of Crimson+Pro:ital,wght@0,700;1,700?

36 Views Asked by At

I am trying to figure out how to take this Google Font API JSON metadata, which lists all the fonts like this:

{
  "family": "ABeeZee",
  "id": "abeezee",
  "subsets": ["latin", "latin-ext"],
  "weights": [400],
  "styles": ["italic", "normal"],
  "unicodeRange": {
    "latin-ext": "U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF",
    "latin": "U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"
  },
  "variants": {
    "400": {
      "italic": {
        "latin-ext": {
          "url": {
            "woff2": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCUnJ8DKpE.woff2",
            "woff": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCUnJ8FOJKuGPLB.woff",
            "truetype": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklQ.ttf"
          }
        },
        "latin": {
          "url": {
            "woff2": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCUkp8D.woff2",
            "woff": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCUkp8FOJKuGA.woff",
            "truetype": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklQ.ttf"
          }
        }
      },
      "normal": {
        "latin-ext": {
          "url": {
            "woff2": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN2tukkIcH.woff2",
            "woff": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN2tuklpUEGpCeGQ.woff",
            "truetype": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tI.ttf"
          }
        },
        "latin": {
          "url": {
            "woff2": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN2tWkkA.woff2",
            "woff": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN2tWklpUEGpA.woff",
            "truetype": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tI.ttf"
          }
        }
      }
    }
  },
  "defSubset": "latin",
  "lastModified": "2022-09-22",
  "version": "v22",
  "category": "sans-serif"
}

And convert it into a URL request using the CSS2 API, like this (e.g. for Crimson Pro Bold & Bold Italic):

https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,700;1,700

What I don't get is the exact meaning of the 0 and 1 in that URL, which is not present in the JSON metadata I linked to. How do I figure out what 0 or 1 value I need from the JSON?

This link https://fonts.google.com/knowledge/glossary/italic_axis says the spec defines the "ital" italic axis as defaulting to 0 (the minimum), maxing out at 1, with a step of 0.1. What does that even mean? Is 0 no italics and 1 is full italics?

If so, what does ital,wght@0,700;1,700 mean?

  • There are two items on the left, ital and wght, and two items on the right 0,700 and 1,700.
  • I get on the left we are requesting for italics and the "regular" font (called wght).
  • I see 700 means the weight, so probably 700 (bold) italic, and 700 bold regular.
  • But what does the 0 and 1 mean?

How do I figure out when I should add a 0 or 1 to the URL I'm going to generate from this JSON metadata?

It seems to get more complicated too, as per this example:

Recursive:slnt,wght,CASL,CRSV,[email protected],300..1000,0..1,0..1,0..1

So basically, I would like to know how to fetch any font properly using the CSS2 API, given the JSON metadata above. I only care about getting the 4 main fonts: regular, bold, italic, and bold italic, when they are available FYI.

1

There are 1 best solutions below

0
herrstrietzel On BEST ANSWER

ital(ic) axis

In fact, the ital query parameter can be considered a boolean value.

You can't interpolate between upright designs and true italics – so the documentation claiming there are intermediate steps of 0.1 is wrong – it is not a proper design axis like wght, wdth or slnt. Variable fonts also allow more fine grained controls (if meticulously defined by the font designer) to get a intermediate design: so it's not just simple linear interpolation (... but most of the time it is based on control point interpolation).

"static" (one style/weight per file) queries

  • font-style and font-weight (in CSS lingo) are grouped and separated by a ; (semicolon)
  • font-style and font-weight are separated by a , (comma)
ital,wght@0,700;1,700

translates to load font in:

  • bold (italic: false; weight: 700) and
  • bold italic (italic: true; weight: 700)

Variable font queries

Your second example queries a variable font – note the .. separators to define a axis range

Recursive:slnt,wght,CASL,CRSV,[email protected],300..1000,0..1,0..1,0..1

You can interpolate in a "slant" axis which has a similar effect (also commonly referred to as "oblique" – the main geometry is retained of a glyph). True italics however are separate font files - that's why you need separate tuples.

You may have a look at this example generating google font API queries for variable (if available) and static fonts:

const baseUrl = `https://fonts.googleapis.com/css2?family=`;
let fontAPiJson =
  "https://cdn.jsdelivr.net/gh/herrstrietzel/fonthelpers@main/json/gfontsMeta.json";
// default selection
let fontFamily = "Open Sans";

(async() => {
  let fetched = await fetch(fontAPiJson);
  // convert tp parsable JSON
  let metaTxt = await (await fetched).text();
  let fontItems = JSON.parse("[" + metaTxt + "]")[0].familyMetadataList;
  //console.log(fontItems)

  /**
   * populate font list
   */
  populateFontList(fontItems);

  function populateFontList(fontItems) {
    let options = "";
    fontItems.forEach((font) => {
      options += `<option>${font.family}</option>`;
    });
    fontsDataOptions.innerHTML = options;
  }

  /**
   * update fonts
   */
  inputFont.addEventListener("input", (e) => {
    fontFamily = e.currentTarget.value;
    getFontItemUrls(fontItems, fontFamily);
  });

  /**
   * init
   */
  inputFont.value = fontFamily;
  inputFont.dispatchEvent(new Event("input"));

  //console.log(fontItems  )
  function getFontItemUrls(fontItems, fontFamily) {
    let fontItem = fontItems.filter((item) => item.family === fontFamily)[0];
    let googleQueryParams = getGoogleFontQueryMeta(fontItem);
    let urlStatic = googleQueryParams.static ?
      baseUrl + googleQueryParams.static :
      "";
    let urlVariable = googleQueryParams.variable ?
      baseUrl + googleQueryParams.variable :
      "";
    a_meta.href = urlVariable;
    a_meta.textContent = urlVariable;
    a_meta_static.href = urlStatic;
    a_meta_static.textContent = urlStatic;
  }
})();

/**
 * compatible with
 * https://fonts.google.com/metadata/fonts
 */

function getGoogleFontQueryMeta(fontItem, variable = true) {
  //console.log(item);
  let fontFamily = fontItem.family;
  let fontfamilyQuery = fontFamily.replaceAll(" ", "+");
  // prepended tuples
  let queryPre = [];
  let queryParams = {
    variable: "",
    static: ""
  };
  // count weights
  let styles = Object.keys(fontItem.fonts);
  let weightsItalic = [];
  let weightsRegular = [];
  styles.forEach((style) => {
    if (style.includes("i")) {
      weightsItalic.push(parseFloat(style));
    } else {
      weightsRegular.push(parseFloat(style));
    }
  });
  // is variable
  let axes = fontItem.axes;
  let isVF = axes && axes.length ? true : false;
  if (isVF) {
    //console.log(axes, ranges)
    // sort axes alphabetically - case sensitive ([a-z],[A-Z])
    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.min + ".." + val.max;
    });
    //  italic and regular
    if (weightsItalic.length && weightsRegular.length) {
      queryPre.push("ital");
      rangeArr = [];
      for (let i = 0; i < 2; i++) {
        rangeArr.push(`${i},${ranges.join(",")}`);
      }
    }
    // only italic
    else if (weightsItalic.length && !weightsRegular.length) {
      queryPre.push("ital");
      rangeArr = [];
      rangeArr.push(`${1},${ranges.join(",")}`);
    }
    // only regular
    else {
      rangeArr = [];
      rangeArr.push(`${ranges.join(",")}`);
    }
    // add axes tags to pre query string
    axes.map((val) => {
      return queryPre.push(val.tag);
    });
    queryParams.variable =
      fontfamilyQuery +
      ":" +
      queryPre.join(",") +
      "@" +
      rangeArr.join(";") +
      "&display=swap";
  }
  /**
   * get static
   */
  queryPre = [];
  if (weightsItalic.length) {
    queryPre.push("ital");
    queryPre.push("wght");
  } else if (!weightsItalic.length && weightsRegular.length) {
    queryPre.push("wght");
  }
  let query = queryPre.join(",") + "@";
  // italic and regular
  if (weightsItalic.length && weightsRegular.length) {
    query +=
      weightsRegular
      .map((val) => {
        return "0," + val;
      })
      .join(";") +
      ";" +
      weightsItalic
      .map((val) => {
        return "1," + val;
      })
      .join(";");
  }
  // only italic
  else if (weightsItalic.length && !weightsRegular.length) {
    query += weightsItalic
      .map((val) => {
        return "1," + val;
      })
      .join(";");
  }
  // only regular
  else {
    query += weightsRegular
      .map((val) => {
        return val;
      })
      .join(";");
  }
  // generate URL query
  queryParams.static = fontfamilyQuery + ":" + query + "&display=swap";
  return queryParams;
}
body {
  font-family: sans-serif
}

legend {
  font-weight: bold;
}

input {
  width: 100%
}

a {
  word-break: break-all
}
<fieldset>
  <legend>Get font CSS </legend>
  <input id="inputFont" type="text" list="fontsDataOptions" placeholder="Search for google font">
  <datalist id="fontsDataOptions"></datalist>


</fieldset>
<p><strong>Variable font URL: </strong>
  <a id="a_meta" href=""></a>
</p>
<p><strong>Static font URL:</strong>
  <a id="a_meta_static" href=""></a>
</p>

In the above example I'm using a static copy of the meta json data - so it's not up-to-date.

Parameter sorting

Worth noting axis identifiers must be ordered alpha-numerically so [a-z][A-Z] [0-9]

https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,400;0,700;1,400;1,700

works!

https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,700;0,400;1,400;1,700

doesn't work!

So the API query structure is quite unforgiving.

Frankly, I prefer the quite similar approach using the developers API. as it also gives you the ability to get truetype fonts or ignore variable fonts.