Why are so many sites using the Dummies font? (but are they really?)

This is another rambling article where I try to figure out why some weird browser thing is happening. The last one of these I did generated the most feedback of anything I posted in 2022. So I guess people like this kind of thing. I like figuring out strange problems. Everyone's a winner here. This time around I'm going to solve the mystery of why so many web pages are rendering with the Dummies font.

Quick disclaimer - there are links to other pages in this article, they may have changed or disappeared in the time since I posted this. I also quote some code snippets from the Chromium project which may be out of date by the time you're reading this. The license for all these Chromium snippets is here: https://github.com/chromium/chromium/blob/main/LICENSE.

Among the weird data hoarding problems I have, fonts rank highly. I don't work in graphic design, I assume that's obvious to anyone visiting this page. I just enjoy making different title images for the various things I post here. That's all it is. At some point I downloaded a knock-off Dummies font that you'll see at the top of this page. I probably downloaded it 20 years ago for a tutorial I never finished. It's still online and you know how to use a search engine if you want it for yourself. Since it hasn't received a take down letter maybe that means it's even technically legal. It will later be important to remember that I have this font installed locally.

Recently, I've been seeing a number of web sites render using Dummies font. Like this:

Site rendering with the Dummies font - 1

OK, maybe GameFAQs is having some fun here. They're a help site after all. Fine. What about this one?

Site rendering with the Dummies font - 2

That doesn't make any sense. It's not the only one. About once a week I stumble over a new (to me) site using the Dummies font. Another example:

Site rendering with the Dummies font - 3

Let's double check that it is the Dummies font being rendered.. yeah, it sure looks like it:

Site rendering with the Dummies font - 4

That can't be what they are actually setting in css right? Let's take a closer look:

Inspecting css

OK, they really want to use the Roboto font. I'm not an expert (yet) on how src:local(..) is supposed to work. It's followed by a URL with a hosted path to the font file. This is telling the browser "hey, if you have a copy of [font name] locally please use that to save me bandwidth, otherwise I guess you can download it here". Except they're not providing a font name. Maybe they're expecting the browser to have some failback method that looks at font-family? Whatever they think should happen is clearly not.

Let's see what happens if we type "Roboto" into that empty src:local(..) part:

Modifying css

Huh. Just one letter in and we have the right font now.

Let's create a quick test page and kick the tires on this:

Test page - blank font name

With a blank font name in src:local(..) Chrome loads the Dummies font and Firefox loads the font named in font-family.

What if we use a font name that doesn't match the font-family?

Test page - incorrect font name

OK. This is kind of odd but probably expected. The font name in src:local(..) overrides the font-family declaration.

Let's focus on Chrome now. What happens with an invalid font name?

Test page - invalid font name

It's hard to read on this screen but it's failing to Roboto Bold for the header and Roboto for the rest. That suggests font-family+font weight are used to determine a failback font. If we give it the correct font name it works as expected:

Test page - valid font name

At this point I think I've narrowed down the problem to:

  1. Some sites don't know (and certainly don't test) how src:local(..) works.
  2. They are using src:local("") under the mistaken assumption the browser will figure out the right local font.
  3. That definitely works for them in Firefox. It may work in old-school Internet Explorer (not the modern Chromium-based version) and Safari. These other browsers are beyond the scope of what I want to test. By the end of this article I think I will conclude that Firefox is actually doing the wrong thing.
  4. If Chrome (or Firefox) finds a match in src:local(..) they use it, even if it isn't really the right font.
  5. If Chrome fails to find a font named in src:local(..) it has some way to look for a failback or default font.
  6. So either (a) the failback is returning the wrong font when a blank name is passed or (b) it is correctly matching an installed font that happens to have a blank name.

Right now (b) is the most obvious problem. Does the Dummies font perhaps have a blank name?

Let's look at all the installed fonts with:


fc-list -v > fc.out

There's nothing on my machine with a blank name. There is something curious about the entry for the Dummies font though:


Pattern has 23 elts (size 32)
	family: "Dummies"(s)
	familylang: "en"(s)
	style: "Regular"(s)
	stylelang: "en"(s)
	fullname: "Dummies"(s)
	fullnamelang: "en"(s)
	slant: 0(i)(s)
	weight: 80(f)(s)
	width: 100(f)(s)
	foundry: "RUDY"(s)
	file: "/home/[..]/.fonts/Dummies.ttf"(s)
	index: 0(i)(s)
	outline: True(s)
	scalable: True(s)
	charset: 
	0000: 00000000 ffffffff ffffffff 7fffffff 00000000 ffffdffe ffffffff ffffffff
	0002: 00000000 00000000 00000000 00000000 00000000 00000000 150002c0 00000000
	0003: 00000000 00000000 00000000 00000000 00000000 10000000 00000000 00000000
	0020: 00010000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
	0022: 02000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
(s)
	lang: aa|ay|bi|br|ch|da|de|en|es|eu|fj|fo|fur|fy|gd|gl|gv|ho|ia|id|ie|io|is|
	it|lb|mg|nb|nds|nl|nn|no|nr|oc|om|pt|rm|sma|smj|so|sq|ss|st|sv|sw|tl|ts|uz|vo|
	wa|xh|yap|zu|an|fil|ht|jv|kj|kwm|li|ms|ng|pap-an|pap-aw|rn|rw|sc|sg|sn|su|za(s)
	fontversion: 65536(i)(s)
	fontformat: "TrueType"(s)
	decorative: False(s)
	postscriptname: ""(s)
	color: False(s)
	symbol: False(s)
	variable: False(s)

So it has a family name and a fullname. It seems like family is what you'd search on. The blank postscriptname is intriguing. It's the only font on my entire system with a blank postscript name. I suspect that might be the problem.

Let's see if anyone else has this issue.. hmm, there's... https://github.com/majodev/google-webfonts-helper/issues/124

We have experienced problems with the empty src: local('') declaration. There was one machine which would not load the webfonts, instead it would always use the font 'Dummies' which is locally installed. The problem occurs in Chrome and Edge browsers but not in FireFox.

It seems like they're reporting this bug to the wrong project (is it even a bug though?). The fix in this project was to simply remove their src: local('') declaration. Neat. It's nice to see another example of this.

Looks like we're going to have to poke around the Chromium source code. I zeroed in on the font cache. To save time Chromium caches fonts in memory after they're loaded from the file system. This seems like the best starting point because I assume Chrome looks in the font cache first. If there is not a hit it should then try to find & load the font.

The entry point for finding a local font appears to be a method called IsPlatformFontUniqueNameMatchAvailable. Right away there is an interesting comment. From https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/fonts/font_cache.h:


  // Should be used in determining whether the <abc> argument to local in
  // @font-face { ... src: local(<abc>) } are available locally, which should
  // match Postscript name or full font name. Compare
  // https://drafts.csswg.org/css-fonts-3/#src-desc
  // TODO crbug.com/627143 complete this and actually look at the right
  // namerecords.
  bool IsPlatformFontUniqueNameMatchAvailable(
      const FontDescription&,
      const AtomicString& unique_font_name);

I wonder if this is related?

Let's look at the bug over at https://bugs.chromium.org/p/chromium/issues/detail?id=627143:

Issue 627143: @font-face src: local() should take *font face* name, not *family* name

Hmm.. not the same but similar. Let's drill down a level and see what the font cache is calling. That takes us to https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/fonts/linux/font_unique_name_lookup_linux.cc:


sk_sp<SkTypeface> FontUniqueNameLookupLinux::MatchUniqueName(
    const String& font_unique_name) {
[...]

  if (!Platform::Current()
           ->GetSandboxSupport()
           ->MatchFontByPostscriptNameOrFullFontName(
               font_unique_name.Utf8(WTF::kStrictUTF8Conversion).c_str(),
               &uniquely_matched_font))
    return nullptr;
[...]
}

In the Chromium source there are versions of this class for Windows, Mac, and Linux. The Linux version is calling MatchFontByPostscriptNameOrFullFontName. OK, that sounds like something that might lookup fonts by postscriptname. We need to track down how that works.

Hopping further down to https://github.com/chromium/chromium/blob/main/content/child/child_process_sandbox_support_impl_linux.cc:


bool WebSandboxSupportLinux::MatchFontByPostscriptNameOrFullFontName(
    const char* font_unique_name,
    gfx::FallbackFontData* fallback_font) {
  TRACE_EVENT0(
      "fonts",
      "WebSandboxSupportLinux::MatchFontByPostscriptNameOrFullFontName");

  font_service::mojom::FontIdentityPtr font_identity;
  std::string family_name;
  if (!font_loader_->MatchFontByPostscriptNameOrFullFontName(font_unique_name,
                                                             &font_identity)) {
    return false;
  }
[.. failback logic ..]
}

Again, not an expert on Chromium here. I see a trace event reference and wonder if restarting Chrome with trace logging would help track this down. I suspect it generates too much noise.

Looks like we need to look at the font loader next. We'll start here https://github.com/chromium/chromium/blob/main/components/services/font/public/cpp/font_loader.cc:


bool FontLoader::MatchFontByPostscriptNameOrFullFontName(
    std::string postscript_name_or_full_font_name,
    mojom::FontIdentityPtr* out_identity) {
  return thread_->MatchFontByPostscriptNameOrFullFontName(
      std::move(postscript_name_or_full_font_name), out_identity);
}

Chromium has a lot of layers. That is not surprising. It looks to me, again not an expert, like the FontLoader class has a reference to a thread that does the actual work. This leads us to the next layer at https://github.com/chromium/chromium/blob/main/components/services/font/public/cpp/font_service_thread.cc:


void FontServiceThread::MatchFontByPostscriptNameOrFullFontNameImpl(
    base::WaitableEvent* done_event,
    bool* out_valid,
    std::string postscript_name_or_full_font_name,
    mojom::FontIdentityPtr* out_font_identity) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  if (!font_service_.is_connected()) {
    *out_valid = false;
    done_event->Signal();
    return;
  }

  pending_waitable_events_.insert(done_event);
  font_service_->MatchFontByPostscriptNameOrFullFontName(
      std::move(postscript_name_or_full_font_name),
      base::BindOnce(
          &FontServiceThread::OnMatchFontByPostscriptNameOrFullFontNameComplete,
          this, done_event, out_valid, out_font_identity));
}

This is my first time looking the Chromium source, I hope that's clear. From what I can gather, this appears to be kicking-off a background thread to load the font asynchronously. I wonder if there are protections in place to prevent a site maliciously loading 1000s of fonts? What good would that attack do though? All you'd accomplish is annoying someone.

Anyway, let's keep drilling down to https://github.com/chromium/chromium/blob/main/components/services/font/font_service_app.cc:


void FontServiceApp::MatchFontByPostscriptNameOrFullFontName(
    const std::string& family,
    MatchFontByPostscriptNameOrFullFontNameCallback callback) {
  TRACE_EVENT0("fonts",
               "FontServiceApp::MatchFontByPostscriptNameOrFullFontName");

  absl::optional<FontConfigLocalMatching::FontConfigMatchResult> match_result =
      FontConfigLocalMatching::FindFontByPostscriptNameOrFullFontName(family);
  if (match_result) {
    uint32_t fontconfig_interface_id = FindOrAddPath(match_result->file_path);
    mojom::FontIdentityPtr font_identity(mojom::FontIdentity::New(
        fontconfig_interface_id, match_result->ttc_index,
        match_result->file_path));
    std::move(callback).Run(std::move(font_identity));
    return;
  }
  std::move(callback).Run(nullptr);
}

I think we're getting close. We're down to a class that has "Font", "Local", and "Matching" in the name. This has to be the last file we look at https://github.com/chromium/chromium/blob/main/components/services/font/fontconfig_matching.cc:


absl::optional<FontConfigLocalMatching::FontConfigMatchResult>
FontConfigLocalMatching::FindFontByPostscriptNameOrFullFontName(
    const std::string& font_name) {
  // TODO(crbug.com/876652): This FontConfig-backed implementation will
  // match PostScript and full font name in any language, and we're okay
  // with that for now since it is what FireFox does.
  absl::optional<FontConfigLocalMatching::FontConfigMatchResult>
      postscript_result =
          FindFontBySpecifiedName(FC_POSTSCRIPT_NAME, font_name);
  if (postscript_result)
    return postscript_result;

  return FindFontBySpecifiedName(FC_FULLNAME, font_name);
}

Ah, so there we go. Chrome looks by postscriptname name first. If it finds a match it returns that font, otherwise it tries the full font name. If neither are found it has some failback logic waaay back at the original calling method. I don't need to examine that because I already have the answer I want. If this is passed a blank font name, and you have a font with a blank postscriptname installed, that font will be returned.

So there are three things being done wrong here:

  1. Sites using src:local("") in their font declarations seems bad. They're expecting the browser failback font lookup to solve a problem for them.
  2. Chrome's method of matching fonts allows it to find a font with a blank postscriptname. I don't think that's wrong. Since Dummies is the only font (that I have) with a blank postscriptname it must be the wrong part in this.
  3. Me installing this sketchy font in the first place.

Alright, that was fun. I suppose the only way to fix this is to uninstall the Dummies font. Although keeping it gives me a small amount of entertainment whenever I see a site render in it. There's something funny in thinking the site is doing it on purpose, even though we now know they are making a small css mistake.



Related