Newsletter, July 2023
System Font Support and the Ubuntu Summit

Another month has come and gone, bringing us to Gio v0.2.0 with system fonts and configurable line height.

System font support required some breaking changes. See gioui.org@v0.2.0 for details and a migration guide.

Additionally, I’m excited to announce that Gio was invited to attend the 2023 Ubuntu Summit in Riga, Latvia. I’ll be attending to give a presentation and workshop about how and why to build applications with Gio. If you’re in the area, attendance is free, but spots are limited. Perhaps I’ll see you there!

Sponsorship

This month, Gio thanks the following organizations and community members for their ongoing support!

Supporting the whole team:

Supporting a maintainer:

Sponsorship money given to Gio enables Elias and I to cover the costs of running Gio’s infrastructure, to pay for some of our time spent maintaining and improving Gio, and to plan for funding significant feature work. You can support Gio by contributing on OpenCollective or GitHub Sponsors.

gioui.org@v0.2.0

Shaper and Theme API Changes

Gio can now load system fonts during the construction of a text shaper. This works by creating an index of the available fonts on the host system. This index is persisted in an OS-specific cache directory for reuse, meaning that your application only pays to construct it on its first execution.

As applications can now leverage system fonts, many apps may no longer need to bundle fonts. In the interest of making text shaper configuration explicit, the APIs for material.NewTheme and text.NewShaper have both changed.

text.NewShaper now accepts text.ShaperOptions to configure its behavior. The default options will load only system fonts, but you can provide additional font collections with text.WithCollection([]text.FontFace). You can prevent system fonts from loading with text.NoSystemFonts().

In the interest of not forcing material.NewTheme to change every time the text shaper API changes, we’ve removed all parameters from material.NewTheme. The returned theme will automatically use a system-font-only text shaper that will initialize itself on first use.

If you wish to configure the text shaper for a *material.Theme, you can do so immediately after constructing it:

th := material.NewTheme()
th.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))

You do not pay for constructing the shaper twice because the initial shaper was never used (and is lazily initialized).

To convert a v0.1.0 invocation of text.NewShaper into a new shaper with system fonts and your font collection:

gofmt -r 'text.NewShaper(a) -> text.NewShaper(text.WithCollection(a))' .

Sadly, gofmt -r does not seem expressive enough to create a rewrite rule to automatically fix calls to material.NewTheme, but the code samples above hopefully illustrate how to do it.

NOTE: On Android, the text shaper cannot initialize with system fonts support unless it is constructed after a Gio window has opened. This is because opening a Gio window initializes some important runtime configuration used to find the system font index. Portable applications should always wait to construct a text shaper until after a window has opened.

Font API Changes

Another breaking change is the removal of font.Font.Variant. Gio applications used to use this field to request monospace or smallcaps text explicitly. However, fonts do not advertise properties like monospace or smallcaps consistently in a way that is performant to check. As a result, we cannot efficiently check for all monospace or smallcaps system fonts. Other font APIs (like the web) instead use font family names to request fonts with specific properties (such as being monospace). Gio application authors can now do the same within the font.Font.Typeface field. Instead of providing the name of a single typeface, you can now provide a comma-delimited list of typefaces that will be tried in order.

Examples:

// Some serif fonts.
font.Typeface = `Times New Roman, Georgia, serif`
// System default fonts for Android, iOS/macOS, Windows, and some Linux installs.
font.Typeface = `Roboto, SF Pro, Segoe UI, Dejavu, sans-serif`
// Monospace fonts
font.Typeface = `Courier, Inconsolata, Roboto Mono, Go Mono, monospace`

The following generic font families can be used (and will automatically be expanded into known common fonts with the right characteristics):

  • fantasy
  • math
  • emoji
  • serif
  • sans-serif
  • cursive
  • monospace

To migrate your application away from font.Font.Variant, explicitly request a font.Font.Typeface with the properties you need and delete the use of Variant entirely.

Line Height API

In addition to system fonts, you can now control the line height of widget.Label, widget.Editor, and widget.Selectable (and their equivalents in package material). They have two fields for this:

  • LineHeight unit.Sp: the unscaled vertical distance between adjacent lines of vertical text. If left as zero, the default value is the text size.
  • LineHeightScale float32: a scaling factor applied to the value of LineHeight to calculate the final vertical distance between lines. If left zero, the default value is 1.2.

Most application authors will only want to use one of these two values, but the API is structured so that you get predictable results if you do choose to use both.

For those writing custom text widgets, text.Parameters exposes these two fields as well.

GIODEBUG Environment Variable

To ease in debugging font selection, Gio now checks for a GIODEBUG environment variable at runtime. If no known value is set, the usage will be printed to stderr:

Usage of GIODEBUG:
        A comma-delimited list of debug subsystems to enable. Currently recognized systems:

        - text: text debug info including system font resolution
        - silent: silence this usage message even if GIODEBUG contains invalid content

We hope to add capabilities to this over time to enable greater debuggability and runtime introspection within Gio applications.

Misc Changes

Finally, Lucas Rodrigues added support for editor password hints on WASM/Android, and Egon Elbre fixed a compilation issue on Windows. Thanks to you both!

Breaking Changes by Author

Chris Waldon:

  • font{,/{opentype,gofont}},text: [API] drop monospace font metadata. 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. 15031d0b
  • text,widget: [API] implement consistent, controllable line height. This commit ensures that any given paragraph of text shaped by Gio will use a single internal line height. This line height is determined (by default) by the text size, rather than the fonts involved. This is a breaking change, as previously we would blindly use the largest line height of any font in a line for that line, leading to lines within the same paragraph with extremely uneven spacing. This commit also updates some test expectations in package widget. 6ea4119a
  • go.*,text,font{,/opentype},app,gpu,widget{,/material}: [API] load system fonts. 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. 43c47f08

Non-Breaking Changes by Author

Chris Waldon:

  • text: fix bitmap y offset computation. This commit fixes a bug that would incorrectly baseline bitmap glyphs text if the line contained another font with a taller line height. The logic for computing the y offset of the glyph incorrectly assumed that the Glyph.Ascent was particular to the glyph instead of the line. I’ve updated it to use a glyph-specific value. 5606a961
  • go.mod,.builds/*: update to Go 1.19. We only support the most recent two go versions, and using 1.18 prevents use of atomic.Bool, failing CI for a different patchset of mine. df782ea7
  • app: [Android] ensure data dirs are set by window init. This commit alters the android backend to automatically populate some environment variables as early as possible in application startup. Specifically, this commit sets the XDG_{CONFIG,CACHE}_HOME environment variables which are necessary for the text shaper to infer a valid cache file location. 92bc52c2
  • app,internal/debug: define GIODEBUG env var. This commit defines an environment-variable-based debug mechanism allowing users to toggle various debug features of their applications at runtime. The only currently supported features are debug logging in the text stack and suppressing the usage message that would otherwise be printed if you supplied a malformed GIODEBUG value. The syntax is a comma-delimited list of features right now. To see the usage, set the variable to the empty string (or any other unsupported value): babe7a29
  • text: add family DSL parser. This commit adds a parser for a simple domain-specific language that can express a comma-delimited list of font families within a string. 6384ab60
  • widget/material: allow configuring default typeface on theme. This commit introduces the material.Theme.Face field, which will automatically populate the Font.Typeface in every text widget created using a constructor function in package material. acab5824
  • widget{,/material}: surface line height manipulation. This commit surfaces fields to manipulate the line height of all label and editor types. It’s unfortunate how this spreads through the API, but I don’t see a good way to eliminate that right now. ddf770b9
  • text: handle shaping string containing only newline. This commit ensures that we properly handle the case in which an input string is only a newline character. We now make a run of text by shaping a space rune and then drop the glyph/rune data from the space (keeping the line height and such). The prior behavior would shape zero runes, resulting in no output runs, and thus our logic for synthesizing a glyph for the newline would never execute while iterating the runs. 36a39f7d
  • go.*: update go-text. This commit updates our version of go-text to pick up important bugfixes to the line wrapper (fixing some fuzzer-discovered bugs). 79668325
  • text: commit important fuzz failure test data. 1d8b5489
  • text,widget: remove fractional line height. The previous logic kept the y offset of a line as a fractional value until the last possible moment in an effort to be as true to a fractional line height as possible (minimize the error), but this interacts pathologically with multi-line text selections, as the selections may have visibly different gaps between lines. It’s better to always shift lines by a fixed quantity of whole pixels, even if it is technically less accurate to the desired line height. 8dc03ed6
  • widget: simplify and improve cursor position generation. This commit updates the strategy of our cursor positioning index to eliminate cursor positions after trailing whitespace characters on a line. Eliminating such cursor positions enables us to collapse trailing whitespace visually without impacting the editability of text (this will be done in a future commit). fdd102aa
  • text: drop unused line.bounds. This commit removes the logic that calculates the bounding box of a line. We don’t actually use this information anywhere, so computing it is just a waste of CPU and memory. Widgets arrive at their own bounding boxes from consuming the glyph stream anyway. c7c49c32
  • widget: fix label vertical glyph padding logic. We previously were not handling glyphs that extended vertically beyond the ascent/descent declared by their font. This is done rarely with text fonts, but is apparently common among symbol and emoji fonts. edbf872b
  • text: fix EOF detection at newline boundaries. This commit tests and fixes some edge cases that threw off rune accounting when a newline character was the final rune in the input and the text was being truncated. I imagine they were never previously reported because it’s rare to try to truncate such text. 80da4d6b
  • text: fix zero-width truncated newline rune accounting. This commit fixes another rune accounting issue that only existed when shaping a solitary newline with zero width while truncating the line. 341978db
  • text: update fuzzer to sometimes truncate. This commit updates the shaper fuzzer to try truncating the text, exposing new edge cases. d4141169
  • text: fix additional truncated newline bug. This commit fixes another rune accounting bug that the fuzzer discovered. If we shaped a space in order to acquire line metrics, but the space itself was truncated, we would reset the truncated count to zero. This had the side effect of lying to later logic about whether the truncator run was present at the end of the shaped text. 32f15ede
  • go.*: update go-text for empty string fix. This commit updates us to a version of go-text that correctly provides text dimensions for the empty string when laying it out with width zero. Previously, zero width would result in text with no height. c1d975cc
  • text: ensure truncated consecutive newlines are handled. This commit ensures that multiple newlines in a row still produce expected results when occuring within a truncated string. The problem was that we usually wrap text that is truncated in a way that forces the truncator symbol to appear at the end unless we know we’re on the final paragraph of the input text. This is the right behavior for text that will be displayed, but when shaping a paragraph containing nothing but a newline, we do not want the truncator symbol in our line. I simply had to disable the forced truncation contextually to make it work. 05f0dc25

Lucas Rodrigues:

  • app/io: [android,js] add password keyboard hint. 74a87b10

Egon Elbre:

gioui.org/x@v0.2.0

X received a number of great improvements. Lucas Rodrigues made the WASM build of gioui.org/x/explorer compatible with changes in Go 1.21; Sebastien Binet improved the handling of large arcing strokes, fixed typos, and added Linux support for gioui.org/x/pref/battery; and I updated the text-related packages with bugfixes and system font compatibility.

Non-Breaking Changes by Author

Chris Waldon:

  • go.*,colorpicker,markdown: update to latest gio (system fonts). This commit updates gio-x to be compatible with Gio’s new text API. In particular, the colorpicker and markdown packages now need to request monospace fonts using the typeface instead of the variant property. 1f14944
  • .builds: update go version. 807d0bf
  • richtext: fix richtext tests. This commit fixes some uses of material.NewTheme that I missed in the richtext test suite. cf97c3d
  • notify/macos: drop critical alert attribute. This makes notify-generated notifications not request critical priority, as it both is disruptive to users (bypassing do not disturb) and requires special entitlements for your application. 4b62720
  • styledtext: add regression test for double newline hang. 0d73d86
  • go.*: update to gio v0.2.0. 53a0f77

Sebastien Binet:

  • stroke: add handling of large angles to ArcTo. ca9a802
  • pref/battery: add battery implementation for Linux. This CL adds the battery API implementation for Linux based off the informations provided by /sys/class and /sys/devices. e77d09b
  • outlay: fix typo in outlay.Item. fae61da

Lucas Rodrigues:

  • explorer: make compatible with Go 1.21. ffbcbb2

Jack Mordaunt:

  • eventx: handle nil queues. This commit ensure that nil’ing out a queue, which is a valid convention in Gio, does not cause a panic in the Spy. 2383213

gioui.org/example@v0.2.0

The examples are all using the new system fonts API, but otherwise have no significant changes.

Changes by Author

Chris Waldon:

  • go.*,all: update all examples for system fonts API. This commit updates every example to be compatible with the new system font text API. Mostly this entailed updating how they constructed text shapers and, in a few cases, how they requested monospace fonts. e0848f2
  • .builds: update go version. 39a247a
  • go.*: update gio, gio-x, and typesetting. This commit picks up v0.2.0 of gio and gio-x, and the typesetting version that is known to be good with them. 2eba220

End

Thanks for reading!

Chris Waldon