0

Consider the following example (note that db.onlinewebfonts.com is very slow for me, but if I wait out a half a minute to a minute or so, eventually font file gets downloaded, and text gets styled):

<!DOCTYPE html>
<html>

<head>
  <title>test</title>
  <style>
    @font-face {
      font-family: 'Impact';
      src: url('https://db.onlinewebfonts.com/t/4cbf858ccd9ffd3923bed6843a474080.ttf') format('woff'); /*https://www.onlinewebfonts.com/download/4cbf858ccd9ffd3923bed6843a474080/*/
      font-weight: bold;
      font-style: normal;
    }
    .testfont {
      font-family: 'Impact', sans-serif;
      font-weight: bold;
    }
  </style>
</head>

<body>
<div class="testfont">Testing the font</div>
</body>
<script>
for(let idss=0; idss<document.styleSheets.length; idss++) {
  let dss = document.styleSheets[idss];
  for(let idcss=0; idcss<dss.cssRules.length; idcss++) {
    let cssrule = dss.cssRules[idcss];
    let csstypename = cssrule.constructor.name; // https://developer.mozilla.org/en-US/docs/Web/API/CSSRule/type -> .type is deprecated, use .constructor.name
    if (csstypename == "CSSFontFaceRule") {
      console.log("idss", idss, "idcss", idcss, "src", cssrule.style.getPropertyValue("src") );
    }
  }
}

console.log("fonts.size", document.fonts.size );
console.log("fonts.keys", document.fonts.keys());
console.log("fonts.values", document.fonts.values());
console.log("fonts.keys().next().value.family", document.fonts.keys().next().value.family); // .value is FontFace
console.log("fonts.check Impact", document.fonts.check('bold 12px Impact'));
console.log("fonts.check whatever", document.fonts.check('bold 12px Whatever'));

</script>
</html>

Ultimately, I would like to obtain in JavaScript, the base64 string of the font file that was loaded with the @font-face CSS at-rule. So, the example simply tries to style an example text div with Impact web font; and then the JavaScript code tries to iterate some document properties.

When I run the above example in Firefox, I get an output in JavaScript Console:

idss 0 idcss 0 src url("https://db.onlinewebfonts.com/t/4cbf858ccd9ffd3923bed6843a474080.ttf") format("woff") test.html:30:15
fonts.size 1                          test.html:35:9
fonts.keys FontFaceSetIterator {  }   test.html:36:9 
fonts.values FontFaceSetIterator {  } test.html:37:9
fonts.keys().next().value "Impact"    test.html:37:9
fonts.check Impact false              test.html:38:9
fonts.check whatever true             test.html:39:9

So, I can get to the CSS declaration that imports the font; unfortunately I cannot get to the actual font data - rather, I can get to the FontFace via document.fonts.keys().next().value - but I do not see actual font file data there.

(btw, how do you iterate document.fonts.keys()? When I tried for(let key of document.fonts.keys()) {console.log("key", key)}, I got "TypeError: document.fonts.keys() is not iterable"??)

One problem is that, in Firefox https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/check :

Nonexistent fonts

If we specify a font that is not in the FontFaceSet and is not a system font, check() returns true, because in this situation we will not rely on any fonts from the set: ...

Note: In this situation Chrome incorrectly returns false. This can make fingerprinting easier, because an attacker can easily test which system fonts the browser has.

So, I guess I was lucky that db.onlinewebfonts.com is slow, so I got "fonts.check Impact false" at start - otherwise, if it is loaded, it returns true, and I cannot tell the difference from accessible to non-existing font.

Anyways, let's say I detect document.fonts.check('bold 12px Impact') to be false at start, and wait it out for it to become true. Can I then somehow retrieve the font file contents from document.fonts (at least in new Firefox and Chrome)?

(If it is impossible, then I guess I have to drop the @font-face CSS statements, and go instead with direct JavaScript loading of .ttf etc files, maybe using an external JS library - but I'd much rather keep the @font-face, then read from wherever the requested font data ended up in the browser using JavaScript, if possible).

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • possibly related: https://stackoverflow.com/questions/39071236/how-to-get-the-font-src-url-via-webfont-loader – sdbbs Jul 16 '23 at 15:32

1 Answers1

0

Retrieve file URLs from document.styleSheets

The document.fonts object doesn't contain the font file src property.

You could instead loop through the document's stylesheets and search for @font-face rules like so:

let stylesheets = [...document.styleSheets];
stylesheets.forEach((stylesheet) => {
  let rules = [...stylesheet.cssRules];
  rules.forEach((rule) => {
    // if is font face rule
    if (rule.type === 5) {
      let style = rule.style;
      let src = style
        .getPropertyValue("src")
        .split("(")[1]
        .split(")")[0]
        .replace(/"|'/g, "");

      console.log(src)
      console.log(style)
    }
  });
});
@font-face {
  font-family: 'Impact';
  src: url('https://db.onlinewebfonts.com/t/4cbf858ccd9ffd3923bed6843a474080.ttf') format('woff');
  /*https://www.onlinewebfonts.com/download/4cbf858ccd9ffd3923bed6843a474080/*/
  font-weight: bold;
  font-style: normal;
}

.testfont {
  font-family: 'Impact', sans-serif;
  font-weight: bold;
}

The filtered styl object also contains the src property so we can fetch the external font file via fetch().
I've replaced the font source with a smaller "Open Sans" subset – but it also works with your original db.onlinewebfonts.com URL.

getFontBase64();

function getFontBase64() {
  let newStyle = "";
  let stylesheets = [...document.styleSheets];

  stylesheets.forEach((stylesheet) => {
    let rules = [...stylesheet.cssRules];
    rules.forEach(async(rule) => {
      // if is font face rule
      if (rule.type === 5) {
        // get font file url
        let style = rule.style;
        let src = style
          .getPropertyValue("src")
          .split("(")[1]
          .split(")")[0]
          .replace(/"|'/g, "");

        // fetch font file
        let blob = await (await await fetch(src)).blob();
        // create base64 string
        let base64 = await blobToBase64(blob);

        // create new rule
        let styleArr = [...rule.style];
        newStyle += `@font-face {\n`;

        styleArr.forEach((prop) => {
          let propValue =
            prop !== "src" ?
            rule.style.getPropertyValue(prop) :
            `url("${base64}")`;
          newStyle += `${prop}: ${propValue};\n`;
        });

        newStyle += `}\n\n`;
        fontCss.value = newStyle;
      }
    });
  });
}

//console.log(stylesheets)

function blobToBase64(blob) {
  return new Promise((resolve, _) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.readAsDataURL(blob);
  });
}
@font-face {
  font-family: "Impact";
  src: url("https://fonts.gstatic.com/l/font?kit=memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4ie1wekcx2GVku9nJNag&skey=62c1cbfccc78b4b2&v=v35") format("woff2");
  font-weight: bold;
  font-style: normal;
}

.testfont {
  font-size: 5vmin;
  font-family: "Impact", sans-serif;
  font-weight: bold;
}

textarea {
  width: 100%;
  min-height: 20em;
}
<div class="testfont">Testing the font</div>

<textarea id="fontCss"></textarea>

As already mentioned: db.onlinewebfonts.com is pretty slow.
If your ultimate goal is to speed up your page you should rather download the truetype font directly and maybe convert it to a more compact woff2 file via tool like transfonter..

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34