adrg / sysfont

Golang identification and matching of system fonts
https://pkg.go.dev/github.com/adrg/sysfont
MIT License
43 stars 13 forks source link

Match font weight #4

Open tdewolff opened 3 years ago

tdewolff commented 3 years ago

Excellent library! There is one feature that would be handy for my use case in selecting fonts for https://github.com/tdewolff/canvas, which is selecting the font weight that best fits. For example, if someone is looking for Arial with weight Black, it would be nice to return Arial Black if is exists, otherwise Arial Bold or Arial. Additionally, it might be nice to specify what weight was returned so that it can be manually fixed with a faux-bold style.

adrg commented 3 years ago

Hi @tdewolff. Thank you for your interest in the library.

Excellent library! There is one feature that would be handy for my use case in selecting fonts for https://github.com/tdewolff/canvas, which is selecting the font weight that best fits. For example, if someone is looking for Arial with weight Black, it would be nice to return Arial Black if is exists, otherwise Arial Bold or Arial.

So, the idea is to specify the font name/font family and the weight separately and favor the font family in the font selection instead of searching for substitutes with the same style? Let's say I want Arial Black, but I don't have that one, and I don't have Arial Bold either, but I do have Arial. Should the font selection return regular Arial instead of a relevant substitute font with weight Black, if one exists? Maybe a more generic style parameter or something like that would be useful as well so that users can specify multiple styles like bold italic.

Additionally, it might be nice to specify what weight was returned so that it can be manually fixed with a faux-bold style.

Yes, that's a good idea. A Weight field would be useful on the sysfont.Font struct. Maybe an Italic field as well.

tdewolff commented 3 years ago

Yes, a more generic style parameter that covers weight and italic would be great. Additionally, you could think of adding language support too, much like does fc-match: https://www.mankier.com/1/fc-match#Examples. For example, I need to match a font that supports Devanagari. The Noto font is divided into languages and weight/italic styles.

Another idea would be to specify a list of fonts much like CSS does, comma-separated, to specify fallback fonts. Let me know what you think ;-)

adrg commented 3 years ago

Sorry for the late response. Yeah, specifying fallback fonts seems like a very good idea. I was thinking that something like this should provide enough customization for the matching process:

type FontProps struct {
    Name     string // font name/family query.
    Weight   string // requested weight.
    Italic   bool   // requested italic style.
    Language string // requested language.
}

type MatchOpts struct {
    Fallbacks []FontProps
}

func (f *Finder) MatchByProps(props FontProps, opts *MatchOpts) *Font

The returned Font would have additional fields as well:

type Font struct {
    Family string
    Name string
    Weight string
    Italic bool
    Language string
    Filename string
}

The Weight field of the Font and FontProps structs will probably not be a string. Probably better to define a custom type and some constants to represent it.

Let me know your thoughts regarding this design.

tdewolff commented 3 years ago

That sounds like exactly something I'd need! I agree that a custom type for the weight might be a good idea (similar to what I have in https://github.com/tdewolff/canvas/blob/master/font.go#L19), which seems to be an exhaustive list of weights used in fonts (though not 100% sure).

Regarding the MatchOpts, that seems to be a very flexible way but it doesn't need to be that flexible if it is complicating implementation. E.g. CSS allows to specify a comma-separated list as the font name, and the weight/italic/language would have similar constraints for each of these fallback names. This is the same as your proposed solution but with the Weight, Italic, and Language the same for each fallback font, the ability to pass a comma-separated list of font names.

I'm not sure how browsers do matching, but I guess it returns the first font that matches. Just to add, the importance of language matching is far more important than weight or italic. The latter two can easily be imitated with faux styles, but language not at all. For example, if the font has no Devanagari (Indian) unicode support, it will show only boxes, no matter if the weight/italic style matches.

npillmayer commented 2 years ago

Hi,

I have been pointed to this lib by Taco (@tdewolff) a couple of days ago and greatly appreciate your work!

I do have a similiar requirement as Taco in the medium term. I understand there is currently no work done on this? This would be understandable, as it is actually a surprisingly hard problem (refer to this blog post by Raph Levien), at least in the general case.

I'd put it into a larger perspective, if you don't mind: The go community lacks a text rendering stack with full i18n support, bidi, font-stack, etc. The rust community is quickly moving towards one. One of the first steps to do would be a font discovery library, and I'd consider your lib the best starting point (though I'm currently using go-findfont).

So, instead of every project building its own font stack (Taco is a brave soul and has more or less done it for canvas; I've started some experiments; the Go standard lib has fragments of it), perhaps there would be enough energy between us (and possibly @flopp) to start a relaxed journey toward a font stack? Relaxed meaning a chilled timeline, possibly trying to converge with go-text/font at some point.

Any opinions on this?

Cheers, --Norbert (@npillmayer)

adrg commented 2 years ago

Hi @npillmayer. Thank you for your interest in the project.

I do have a similiar requirement as Taco in the medium term. I understand there is currently no work done on this? This would be understandable, as it is actually a surprisingly hard problem (refer to this blog post by Raph Levien), at least in the general case.

Yes, I haven't started working on this issue due to lack of free time. It is quite high on my to-do list though and I will get around to working on it as soon as possible (which is soon I hope).

The issue is quite complex, especially the part regarding suggesting appropriate fonts depending on the language, or perhaps a set of glyphs which need to be supported. I don't have language information right now. I need to add language info to os-font-list for that first, which is not an easy task.

Of course, there might be better ways to match system fonts, like the ones proposed in the blog post you mentioned. However, for standard (well-known) system fonts, the present approach might suffice. This was the initial goal of this project, to list/match system fonts and to be tolerant to variations in the way the fonts are specified. It was a bit of an experiment at first, but it did get a surprising amount of interest.

Improving the substitution system so that the font family is prioritized over the style should not be that hard. So at least that can be considered progress once it's implemented.

I'd put it into a larger perspective, if you don't mind: The go community lacks a text rendering stack with full i18n support, bidi, font-stack, etc. The rust community is quickly moving towards one. One of the first steps to do would be a font discovery library, and I'd consider your lib the best starting point (though I'm currently using go-findfont).

So, instead of every project building its own font stack (Taco is a brave soul and has more or less done it for canvas; I've started some experiments; the Go standard lib has fragments of it), perhaps there would be enough energy between us (and possibly @flopp) to start a relaxed journey toward a font stack? Relaxed meaning a chilled timeline, possibly trying to converge with go-text/font at some point.

Any opinions on this?

Cheers, --Norbert (@npillmayer)

Seems like there was a lot of work involved in implementing the font package for canvas. I find the project itself to be really great. I think doing some sort of "team-up" is a nice idea. I wouldn't mind that at all, although I don't have that much free time usually so I cannot make any promises regarding my availability. And even then, I maintain other open-source projects as well so my free time is divided between them.