tdewolff / canvas

Cairo in Go: vector to raster, SVG, PDF, EPS, WASM, OpenGL, Gio, etc.
MIT License
1.5k stars 102 forks source link

Font fallback handling #294

Open elwinar opened 6 months ago

elwinar commented 6 months ago

Hey there.

First of all, thanks for your continued work with this library.

I've been doing a bit of text rendering, and I've come across the classic "this font doesn't handle X glyph". Most systems handle this using a fallback font that have more codepoints mapped, but I didn't see an option to do it with this lib. Have I missed the option ? If it is not implemented I'm willing to do it if you give me some pointers to help get started.

tdewolff commented 6 months ago

Hi Romain, thank you for the kind words. What you describe is indeed lacking from this library, but unfortunately involves quite a lot of work. First of all, we'd need to be able to define a list of fallback fonts. Second, the font shaping should pick the first font that contains the given glyph. As far as I can see, there needs to be some refactoring to separate instances where font face is bound to text spans and not to glyphs. I'll have to take a deeper look to give you some useful pointer to be honest...

tdewolff commented 6 months ago

I think we should add type FontFamilies []*FontFamily as a list of fallback font families, as well as type Fonts []*Font. Both should have Face method (like Font and FontFamily) that creates a usable font face. FontFace should keep a list of *Font instead of a single one.

We should probably write a general function Shape(fonts []*Font, text string, ppem uint16, direction text.Direction, script text.Script, language string) []text.Glyph which uses fonts as a list of fonts with the primary first and fallback fonts the rest in order. This would be used in RichText.ToText and NewTextLine. Each glyph uses the first font that provides it. This should split TextSpan at places where the used font changes.

It would be nice if the *SystemFont functions could accept a comma-separated list of system fonts to load, but not sure how to add something similar for loading from font files or from memory.

That's probably it, what do you think?

NB: the shaper uses HarfBuzz, which makes it a bit harder since it's an external dependency. Additionally, it requires to reparse the SFNT fonts which is really inefficient. We can either create our own HarfBuzz port (a lot of duplicate work) or use the Font implementation from typesetting everywhere. A couple of features (including this one?) might not be compatible with that library though (crucially, font subsetting for PDFs).