Page MenuHome

BLF: Fallback Font Stack
ClosedPublic

Authored by Harley Acheson (harley) on Sep 24 2021, 3:47 AM.
Tags
Tokens
"Love" token, awarded by Tetone."Love" token, awarded by Raimund58."Love" token, awarded by lone_noel."Love" token, awarded by mywa880."Love" token, awarded by pablovazquez."Love" token, awarded by HEYPictures.

Details

Summary

Allow use of multiple fonts acting together like a fallback stack,
where if a glyph is not found in one it can be retrieved from another.


A nice overview is also here: https://devtalk.blender.org/t/discussion-of-d12622-fallback-font-stack/23502

Let's start with an example that might not resonate with most viewers, but does the job well. Imagine that you live in North Sumatra and speak a Batak language, which is not supported in Blender. The best font you find is Noto Sans Batak:

But when you install it you find that Blender is not usable. This is because this font contains only Batak glyphs and nothing else, no latin (A-Z) characters, no symbols, nothing. This patch changes this experience from what you see on the left to that on the right:

We are shipping fonts that contain most of the world's characters - 54,000 of them. We allow users to change these fonts, but when you do so they completely replace them rather than augment them. Selecting ANY other font will give you less glyph coverage. If a font does not contain a needed character you will see a blank square (tofu) instead. Missing symbols we use in the interface like ← or ⌘ or ⇧ could result in confusion. Even worse is that some language-specific fonts - almost the entire Noto family - have no latin characters at all! So you can select an ideal font for your language that is unusable with Blender.

With this patch you always have all characters available no matter what font you select. Your font and ours are treated as one.

For testing you will need to ADD the contents of the following zip archive to the files in your datafiles/fonts folder:

It is 26 separate font files, enough font files to cover all of the top 44 languages by number of speakers. This represents about 1.5 billion more people who can view their language in Blender.

Okay, but what is with the huge unicode_blocks structure?

When looking for a glyph I don't want to literally go from font to font to font. Each font contains a set of "coverage bits", a set of four 32-bit flags indicating that it has substantial coverage of that unicode block. It isn't a guarantee of complete coverage, but a very good hint that it probably contains the one you need.

This means when looking for a character code I can use that unicode_blocks structure to quickly find (binary search) the coverage bit corresponding to the the range containing that character code. Then we look only in fonts containing that coverage bit, stopping at the first one. If not found it then looks in the "last resort" font, or "not.def" if not found anywhere.

This means we don't have touch files unnecessarily. With FreeType caching D13137: BLF: Implement FreeType Caching we can have fonts without faces loaded. This would mean that if you never use a particular font, like Javanese, it would not have to be kept in memory at all. So no penalty of having good coverage and the quickest access to the characters you actually use.

Diff Detail

Repository
rB Blender

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
Harley Acheson (harley) added a project: Core.

Updated to the current state of master.

Updated to current state of master (interface.cc), and correct a typo in BLI_wcwidth()

Harley Acheson (harley) edited the summary of this revision. (Show Details)Apr 5 2022, 9:51 PM

Updated to the current state of master.

Updated to the current state of master.

Updated to the current state of master.

Brecht Van Lommel (brecht) requested changes to this revision.Jun 1 2022, 5:52 PM
Brecht Van Lommel (brecht) added inline comments.
source/blender/blenfont/BLF_api.h
58

This doesn't seem like it should be part of the public API. It's only used internally, and in general I would expect thing to work without the public API user knowing about fallback fonts.

source/blender/blenfont/intern/blf.c
1227–1228

I'm not sure it should be trying all fonts here?

Maybe for the way we use the API now this is not a problem, but I think in general you could load arbitrary fonts in arbitrary order, and then it's hard to tell which font you're going to find the glyph in. There could be a flag indicating which the default fonts are, and only test those, or there could be a list of fallback fonts on the default font.

source/blender/blenfont/intern/blf_font_default.c
52

I'm not sure these should be in a separate "fallbacks" folder, I think that term is confusing. We ship a default font that previously was one file, and now it's multiple files. From a user point of view I don't think it makes sense to think of these in terms of a "fallback", it's just more files to complete the font.

To avoid users having custom fonts in this folders that we would load unnecessarily, giving all the fallback fonts the same prefix as the default font would help.

source/blender/blenfont/intern/blf_glyph.c
562–566

It would be more efficient to get the char index from BLF_fallback_font instead of looking it up again here.

If there are not other places that plan to call this function, the whole implementation could also be moved here.

source/blender/editors/interface/interface_style.cc
492

Can BLF_load_mono_default do this, so we don't have to duplicate this call in wm_playanim.c?

496

Does this and BLF_unload_all also need to be called in wm_playanim.c?

As a refactor it might make sense to move more of this code into blenfont, it's confusing to have one module initialize blf_mono_font and another module clear it.

This revision now requires changes to proceed.Jun 1 2022, 5:52 PM
Harley Acheson (harley) marked 4 inline comments as done.Jun 3 2022, 11:22 PM

Many changes to incorporate changes suggested by review.

BLF_fallback_font no longer part of public API. They way that alternative fonts are selected is simpler, easier to follow, and just part of blf_glyph_index_from_charcode. Adding BLF_MONOSPACED inside BLF_load_mono_default, which is simpler.

Still some things to do. Will be next be putting all the fonts in the same folder and not using a "fallback" subfolder. Needs a bit of a think.

Harley Acheson (harley) planned changes to this revision.Jun 4 2022, 12:41 AM
Harley Acheson (harley) edited the summary of this revision. (Show Details)Jun 4 2022, 7:39 PM

Now all the font files are in the datafiles/fonts folder, so there is no longer a need for a separate "fallback" subfolder.

I have updated the patch description and the fonts zip file there. The fonts in the zip are meant to replace all the files currently in the fonts folder. The system just loads "DejaVuSans.ttf" and "DejaVuSansMono.ttf" by name while the rest are loaded blind.

Harley Acheson (harley) marked 2 inline comments as done.Jun 5 2022, 8:29 PM

BLF_load_fallbacks -> BLF_load_font_stack. The individual fonts that comprise the font stack still being marked as "fallback" because that still makes sense. The "font" in "BLF_load_font_stack" might be redundant since in BLF, but reads well.

@Brecht Van Lommel (brecht) - Does this and BLF_unload_all also need to be called in wm_playanim.c?

No, AFAICS that only draws in the mono font to show the file name, frames per second, etc. So it just needs a call to load the rest of the font stack in the unlikely case that the string contains glyphs not in that font.

Brecht Van Lommel (brecht) requested changes to this revision.Jun 7 2022, 3:15 PM

No, AFAICS that only draws in the mono font to show the file name, frames per second, etc. So it just needs a call to load the rest of the font stack in the unlikely case that the string contains glyphs not in that font.

But you still need to call BLF_unload_all in wm_playanim.c then, to unload the entire font stack?

source/blender/blenfont/BLF_api.h
21

This should not be in the public API, but in blf_internal.h.

Also if this is going to be in the global namespace, best name it e.g. blf_global_font to avoid potential naming conflicts.

source/blender/blenfont/intern/blf_font_default.c
50

have be -> have been

61

Handle font loading failure where font_id == -1, and print error in the case.

71

Fallback fonts -> Fonts

source/blender/blenfont/intern/blf_glyph.c
567

This assumes there is only a single last resort font. I guess that's an ok assumption given our current fonts, but could also change the code to loop over the fonts twice, the second time checking the last resort fonts.

569

Only try fonts with BLF_FALLBACK set.

590–591

Only change *font when the glyph is actually found, for consistency.

source/blender/windowmanager/intern/wm_playanim.c
11 ↗(On Diff #52291)

Unintentional change.

This revision now requires changes to proceed.Jun 7 2022, 3:15 PM
Harley Acheson (harley) marked 4 inline comments as done.Jun 7 2022, 8:00 PM

Completing some minor issues.

Updated to incorporate some of the changes suggested by review.

Harley Acheson (harley) planned changes to this revision.Jun 7 2022, 8:21 PM

Have a few things to still look at and fix and change.

source/blender/blenfont/BLF_api.h
21

I'd assume we don't want to include blf_internal.h in BLF_api.h though?

renaming of global_font makes sense but would make a lot of noise in this patch. Maybe leave to the end?

source/blender/blenfont/intern/blf_glyph.c
567

This assumes there is only a single last resort font. I guess that's an ok assumption given our current fonts, but could also change the code to loop over the fonts twice, the second time checking the last resort fonts.

The patch would handle having none or multiple last resorts fonts without error. But there should be no need to check multiple last resorts since all of them always return a glyph for all codepoints. So the first one will always have something.

569

Sounds weird but I have experienced the situation of using the monospaced font and needing a glyph found only in the main variable-width font, and was not otherwise in any fallback font.

We could go through the fonts once looking at the fallbacks, and then go through a second time for this and for another unlikely situation. It is possible to be looking for a glyph that is in a block that has a coverage bit, but is only found in one of your fonts that does not mark that bit as covered (maybe because it doesn't do it well).

I mostly didn't want to make a second loop through them in case it was not recognized how unlikely it would be called.

590–591

This change of font is required here as we will be getting the glyph at that index from the last_resort font. These changes of font are because any charcode can be found at different positions within different fonts (at a glyph index found by FT_Get_Char_Index).

Seems I forgot to submit my comments last week, so sending now.

source/blender/blenfont/BLF_api.h
21

Not sure why we would include blf_internal.h in BLF_api.h, the point is that global_font is internal to the module and should not be in the public API.

You could do the renaming in a follow up commit.

source/blender/blenfont/intern/blf_glyph.c
569

What we want is for it to look in the default font, which consists of a multiple font files.

We do not want it to look in some arbitrary font that the API user has loaded with BLF_load for unrelated reasons.

So a better solution could be to rename BLF_FALLBACK to BLF_DEFAULT and also tag the default font with that, and then only try those fonts.

I think the fact that we have a default font that consists of multiple files on disk should be abstracted away from the API user entirely.

590–591

I understand that the font pointer must be changed, but only if the glyph is successfully found, same as is done a few lines above.

Harley Acheson (harley) marked 6 inline comments as done.Jun 14 2022, 7:30 PM

Completing current review items

@Brecht Van Lommel (brecht)

...the point is that global_font is internal to the module and should not be in the public API

Seems obvious now, but I totally misunderstood what you were asking. Done properly now.

...rename BLF_FALLBACK to BLF_DEFAULT and also tag the default font with that, and then only try those fonts

Yes, that works great. If we ever want to hide the stack members from display lists we could always add a flag then.

I understand that the font pointer must be changed, but only if the glyph is successfully found, same as is done a few lines above

No worries, did it just like that.

Just a slight change to the range of codepoints treated as double-width when monospaced.

Harley Acheson (harley) edited the summary of this revision. (Show Details)Jun 15 2022, 7:24 PM

Updated the "fonts.zip" file in the description to include Noto Emoji. This range does include more than emojis, so lots of symbols that could used for add-ons.

Brecht Van Lommel (brecht) requested changes to this revision.Jun 15 2022, 7:29 PM

This comment was not addressed yet:

But you still need to call BLF_unload_all in wm_playanim.c then, to unload the entire font stack?

Otherwise looks fine to me.

This revision now requires changes to proceed.Jun 15 2022, 7:29 PM

We may also want to wait with committing the actual fonts files until D13137 is ready, since loading all fonts could be slow?

But this implementation could land earlier.

@Brecht Van Lommel (brecht) - But you still need to call BLF_unload_all in wm_playanim.c then, to unload the entire font stack?

Yikes, you are right. will take a look at that.

Otherwise looks fine to me.

One thing for me to point out is that most of the fonts I have selected here are "variable" fonts. Generally 50% larger than if I had not. But those type support changing the look of the font along different axes - at least boldness, but quite often others too. This extra file size is definitely worth it if we eventually approve D12977: BLF: Add Support for Variable Fonts and then later use bfont for 3D text curves.

Another thing point out is my inclusion of the material icons font - https://fonts.google.com/icons. I selected this particular one because it matches so well with the feature sizes of our current icons and it is so complete and well-designed. But why select one at all? Mostly because these go into the Private Use area and I'd like to have something in there to reserve the space. Otherwise I worry about an addon author placing a private use font in our fonts folder. If that addon gets popular at all it would undermine us ever using that area. Of course if we ever turn our own icons into a font I'd put it elsewhere in the multiple private use areas.

We may also want to wait with committing the actual fonts files until D13137: BLF: Implement FreeType Caching is ready, since loading all fonts could be slow?

Will test but am assuming the loading will not be noticeable. We no longer do any preloading of glyphs. We used to render and cache all the ascii glyphs immediately, but now avoid doing any work until a glyph is explicitly asked for. Even with D13137: BLF: Implement FreeType Caching the initial loading would be the same since I'd have to load each each font once in order to get coverage bits, but then I'd be able to drop faces knowing they would be reloaded when needed.

I _think_ there is a needed change here. I think the font files in the folder should be read in sorted order. That would make no difference right now as it is, but could offer users a way to address obscure issues, like if they want to use multiple fonts that highly overlap and want them in an order.

Base font names, "DejaVuSans.ttf" and "DejaVuSansMono.ttf", should be defines.

Updated "fonts.zip" in the description to include a variable version of Material Icons.

In D12622#410140, @Harley Acheson (harley) - ....the font files in the folder should be read in sorted order....

Whoops, didn't realize that they are loaded in sorted order. bli_builddir sorts.

Adding some defines for default font names and font folder name.

@Brecht Van Lommel (brecht) - But you still need to call BLF_unload_all in wm_playanim.c then, to unload the entire font stack?

Not that I can see. Not seeing any issues and the player draws that text fine. The font stack is initialized after BLF_init() in that file, and later the BLF_exit() will unload them all.

The only reason for that BLF_unload_all to exist is for interface_style.cc, just to ensure that the fonts are in the correct order in the array when a custom font is selected.

Changing to using woff2 for font types.

@Brecht Van Lommel (brecht) - We may also want to wait with committing the actual fonts files...loading all fonts could be slow?

On my slow old machine (and restarting it between tests) I am seeing an increased loading time of about 400 ms. I found that the loading is faster when using the compressed woff2 files, with the added feature of them taking up less disk space, so updating the "fonts.zip" in the description. We go from 10.4 MB in our two font files now, to 16.6 MB but with double the glyphs and much better language coverage.

But this implementation could land earlier

My hope is that once this seems close enough I would alter this one to add the fallback functionality but still only using our existing two fonts. So no actual change. Although new languages could be added by simply copying a new font in the folder.

Then I would make another patch where I would do my best to sell this package of fonts. Then we can nix anything you don't want, swap some out, etc. I should be able to outline the new languages added, number of glyphs, new utility, etc.

Harley Acheson (harley) edited the summary of this revision. (Show Details)Jun 17 2022, 4:47 AM

Not that I can see. Not seeing any issues and the player draws that text fine. The font stack is initialized after BLF_init() in that file, and later the BLF_exit() will unload them all.

The only reason for that BLF_unload_all to exist is for interface_style.cc, just to ensure that the fonts are in the correct order in the array when a custom font is selected.

Ok, makes sense.

On my slow old machine (and restarting it between tests) I am seeing an increased loading time of about 400 ms.

I found a similar difference in startup performance (with cold caches). It increased from 0.616s to 0.889s, which is quite significant.

sudo sh -c "sync; echo 3 > /proc/sys/vm/drop_caches"; time blender -b

I think the extended coverage is great and disk space usage seems reasonable, just would be good if there was some way to not actually load all the fonts when no glyphs in them are used.

Since you plan to submit the font files for review separately, nothing blocking this patch anyway, besides a minor compiler warning to be fixed.

source/blender/blenfont/BLF_api.h
323

Use (void):

/home/brecht/dev/blender/source/blender/blenfont/BLF_api.h:326:25: warning: this function declaration is not a prototype [-Wstrict-prototypes]
void BLF_load_font_stack();
This revision is now accepted and ready to land.Jun 17 2022, 6:10 PM

Updates:

  1. Missing void argument in function declaration.
  1. Default base names set to current font names, so this can be committed without font file changes.

Even without font file changes these changes still allow a couple cool features. You can change the UI font in Edit / Preferences to a font file that contains few glyphs and still see them all. You can also just place a new font file inside the fonts folder and have it work automatically.

Harley Acheson (harley) edited the summary of this revision. (Show Details)Jun 17 2022, 7:19 PM
This revision was automatically updated to reflect the committed changes.