This commit adds a parser for a simple domain-specific language that
can express a comma-delimited list of font families within a string.
I chose to encode families in this way because the string can be used
as an efficient hash key in a way that a slice of families cannot. Similarly,
using a slice of families would require allocations on the caller side.
The particular format was chosen to allow lists to be written with as little
fanfare as possible. This is why quotation marks are completely optional. It's
easy to read:
Times New Roman, Georgia, serif
Why force the user to type this (this will parse the same):
"Times New Roman", "Georgia", "serif"
I've tried to handle edge cases exhaustively. Commas are legal within quotes.
Within a quoted string, you can escape instances of the surrounding quote with
a backslash, and can escape literal backslashes by adding another backslash.
I wrote the lexer/parser by hand, and I hope that they're both easy to understand
and (if need be) extend.
A side effect of the DSL I've chosen (and part of my reasoning for allowing both
single and double quoted strings) is that CSS font-family rules will generally be
valid font family lists in Gio. This means the syntax is already familiar to users
coming from other technologies, and that you can copy from a web-based application
to get a similar font stack in Gio.
Fixes: https://todo.sr.ht/~eliasnaur/gio/317
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit updates the text package to be able to load system fonts. As a consequence,
application authors may choose to provide no fonts manually, and it's
also possible that the system provides none (WASM, for instance, currently provides no
system fonts). As such, the text stack needed some minor tweaks to handle this case by
displaying blank spaces where text should be rather than crashing when no faces are
available.
Internally, we are dropping the old method of choosing faces and instead relying solely
on the new font matching logic in go-text. I chose to do this because maintaining two
different sets of logic with a hierarchical relationship proved to be really complex,
and also the go-text logic seems to produce higher-quality choices.
The breaking API change from this commit is the new way of constructing a text shaper
using text.ShaperOptions. Providing no options will result in a shaper that uses solely
system fonts. The various options can be used to disable system font loading and to
provide an already-parsed collection of fonts as per Gio's old API.
The material.NewTheme function now accepts no arguments instead of a font collection.
Users wanting to provide a collection can simply provide a new shaper configured how
they would like:
theme := material.NewTheme()
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
This commit touches many packages to fix up their construction of text shapers, mostly in
test code. The changes to the tests in package widget deserve special note:
Changing our font resolution logic caused the tofu characters within the
test strings to use a different font's tofu. This isn't a problem, but shifted
the layout of the shaped text a little bit. I've updated the numbers to expect
the new glyph positions.
Fixes: https://todo.sr.ht/~eliasnaur/gio/309
Fixes: https://todo.sr.ht/~eliasnaur/gio/184
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
In the general case, it isn't possible for us to efficiently find system fonts that
are monospace. Fonts don't advertise being monospace frequently, so the only way to
reliably detect it is to check that all glyphs are the same width. This is expensive,
far too much so to be done on every system font when there may be thousands of them.
Other font resolution systems rely upon the user requesting fonts by their family name.
If you want a monospace font, ask for it by name or use a generic name like 'monospace'.
This will be Gio's approach from here on out.
Existing code relying upon setting Variant="Mono" should instead set Typeface="Go Mono"
(for the Go font) or specify another monospace typeface. The generic face "monospace"
will search for one of a set of known monospace fonts that may be available on the system.
Similarly, smallcaps isn't well advertised and users should rely on requesting all-smallcaps
fonts by typeface. To get the Go smallcaps font, use Typeface="Go Smallcaps".
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit adds back support for loading font collections, which we
lost when switching to the harfbuzz-based shaper last January. In
addition, this commit takes advantage of our new font loading library's
metadata facilities to automatically construct text.FontFaces for all
fonts within a collection. This is significantly more ergonomic for
users, and can be used to load single fonts with automatic metadata
detection as well.
I've exposed a opentype.Face.Font() method that can be used to get the
font metadata for a given face as well, though you have to type assert to
see it:
var myFace text.Face
if asOpentype, ok := myFace.(opentype.Face); ok {
myFont := asOpentype.Font()
}
The one problem with this approach is that the font variant field always
be automatically populated. Mono font detection is supported, but
other variants like SmallCaps are more complicated and may need to be
expressed differently in the future (smallcaps is a feature that any font
file can have, not necessarily a separate font file). See this [0] upstream
issue for details.
Additionally, in order to avoid import cycles, I've moved the declarations
of font attributes to package font. You can fix your code automatically to
refer to the new definitions by running the following:
gofmt -w -r 'text.FontFace -> font.FontFace' .
gofmt -w -r 'text.Variant -> font.Variant' .
gofmt -w -r 'text.Style -> font.Style' .
gofmt -w -r 'text.Typeface -> font.Typeface' .
gofmt -w -r 'text.Font -> font.Font' .
gofmt -w -r 'text.Regular -> font.Regular' .
gofmt -w -r 'text.Italic -> font.Italic' .
gofmt -w -r 'text.Thin -> font.Thin' .
gofmt -w -r 'text.ExtraLight -> font.ExtraLight' .
gofmt -w -r 'text.Light -> font.Light' .
gofmt -w -r 'text.Normal -> font.Normal' .
gofmt -w -r 'text.Medium -> font.Medium' .
gofmt -w -r 'text.SemiBold -> font.SemiBold' .
gofmt -w -r 'text.Bold -> font.Bold' .
gofmt -w -r 'text.ExtraBold -> font.ExtraBold' .
gofmt -w -r 'text.Black -> font.Black' .
gofmt -w -r 'text.Hairline -> font.Thin' .
gofmt -w -r 'text.UltraLight -> font.ExtraLight' .
gofmt -w -r 'text.DemiBold -> font.SemiBold' .
gofmt -w -r 'text.UltraBold -> font.ExtraBold' .
gofmt -w -r 'text.Heavy -> font.Black' .
gofmt -w -r 'text.ExtraBlack -> font.Black+50' .
gofmt -w -r 'text.UltraBlack -> font.ExtraBlack' .
Make sure each affected file imports gioui.org/font.
[0] https://github.com/go-text/typesetting/issues/57
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Before this change, package font implemented a global font registry,
with the usual problems of package global state.
This change deletes the global registry and introduces the text.Collection
type for representing a list of fonts and their faces. Collection exports
Lookup that finds the closest match and its face.
The existing FontRegistry is renamed to Cache to reflect its new limited
functionality: a cache of shapes and measurements on top of a Collection.
Then, material.NewTheme is changed to take a Collection and initialize
a Cache.
Updates gio#19 because multiple windows require a separate (writable) Cache per
window, while (read-only) Collections may be shared.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Package font holds a singleton text.Shaper for general use. Subpackages
call Register to add fonts to the registry.
The intention is that programs can add a typeface by importing a package:
import _ "gioui.org/font/gofont"
Signed-off-by: Elias Naur <mail@eliasnaur.com>