Newsletter, April 2023
Font Collection Support and more

This month saw quite a few font and text improvements and numerous bugfixes across the ecosystem. With support from Plato Team, I focused on making our text stack more efficient with the following results:

  • We again support loading TTC and OTC font collection files, a feature we lost when switching text shapers to support RTL text.
  • We can now infer the font metadata for most fonts. You can use opentype.ParseCollection on collections or individual fonts, and the returned []font.FontFace will have proper values for Typeface, Weight, and Style. The Variant field can currently only detect "Mono" variants, so applications using other variants will need to add the extra info manually.
  • You can now load a font once and share it across all application windows and text shapers safely. The gofont.Collection() function does this automatically (loading the fonts only once no matter how many times you call the function). The gofont package has always tried to do this, but the previous implementation was not guaranteed to be free of data races during text shaping.
  • We now take advantage of an upstream go-text/typesetting feature that caches an expensive font transformation, resulting in a large memory and CPU savings for shaping operations.
  • The default window decorations now use the same loaded copy of “Go Regular” as your application (if you use gofont.Collection()), so there is only one copy in memory.
  • Text editors and selectable text now hit the text shaping cache more efficiently (without performing rune decoding before hitting the cache), resulting in a 8-10% speedup.

I also fixed a major text display bug affecting 32-bit platforms. I’d like to thank Lucas Rodrigues for putting together an excellent bug report and reproducer, and Dominik Honnef for bisecting the offending change. In order to catch problems like this in the future, I’ve updated our CI to run tests on GOOS=linux GOARCH=386 as well as the other configurations.

My text changes did result in a breaking API change. See the section on breaking changes in core gio for details.


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.

Changes by repo

Below you can find summaries and details of all changes across the official project repositories.

core gio

As dicussed above, there were many changes to core’s font/text stack this month. Additionally, Dominik Honnef contributed several bugfixes and performance improvements, Ilia Demianenko made Gio default to higher refresh rates on Android when available, and Elias fixed a bug in key event routing.

Breaking Changes by Author

In order to enable our font parser to infer font metadata for you, all font metadata symbols had to move to package font to avoid an import cycle. As such, you’ll need to run the following on your code:

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.Face -> font.Face' .
gofmt -w -r 'text.Weight -> font.Weight' .
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' .

After the above, many files may unnecessarily import and need to import instead. You can try to fix this automatically with:

go run -w .

Chris Waldon

  • font/opentype: [API] support font collection loading. 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. f77bf9a4

Non-Breaking Changes by Author

Chris Waldon:

  • text,widget: minimize loss of positional precision in shaping. This commit combs through the logic of computing glyph sizes and positions, attempting to remove all unnecessary rounding and truncation. This is in an effort to help text display consistently when different-length strings are displayed near one another. 73787b84
  • go.*,text: update go-text to pick up font transformation caching. This commit picks up improvements in upstream go-text that (among other things) allow the shaper to reuse a lot of information when shaping the same font face multiple times (using an LRU cache to keep that information available). I’ve tried to pick a reasonable default LRU size of 32 faces. 6937a5dd
  • widget/material: make ScrollBarStyle.MajorMinLen default to FingerSize. Egon pointed out that the current default is unusable on touch screens in Slack, so this change should hopefully ensure the indicator is interactable on touch devices. a7c9ca99
  • font/opentype: make reusing font.Face efficient and safe. This commit updates the internal representation of a font to separate the threadsafe and non-threadsafe operations in a way that enures font.Faces can be shared by all text shapers in an application. This should ensure that applications only need to parse fonts a single time, saving a great deal of memory for applications that open many windows (which each need a different text shaper). ccf24c0b
  • font/gofont: allow loading just the regular Go font. This commit introduces a special mechanism to load only the regular version of the Go font. This is useful for Gio to load a font for drawing window decorations without forcing applications to load every Go font. 0dfd8c3d
  • app: use more efficient window decoration font load. This commit switches to the new Regular() collection method in gofont, ensuring that the regular face is only ever loaded once. 880cd27f
  • text: optimize shaper paragraph decoding. This commit removes some inefficiencies from the pre-shaper-cache processing of text. The text is no longer decoded into runes prior to being tested against the cache, and the search for newlines uses slightly more efficient iteration operations now. bba91263
  • widget: update textIterator docs for accuracy. The previous docs claimed that failing to set a textMaterial would result in invisible glyphs when in reality it results in using whatever the current paint material is. This could be the paint material from before laying out the glyphs, or it could be the material for a bitmap glyph. As such, it’s better to say that the color is undefined. 816bda7a
  • ci: run tests for 32-bit architectures. This commit introduces a 32-bit test run to our Linux CI in an attempt to detect architecture dependent bugs earlier. I was forced to install the i386 packages in a build step becuase they can only be added after enabling the architecture. Also GOARCH=386 does not support the race detector, so I’m not running the tests with race detection enabled for that GOARCH. 2a5f8950
  • text: fix 32-bit glyph id packing. This commit fixes a problem in the unpacking of text.GlyphID on 32 bit architectures. Incorrectly casting to an int on those platforms resulted in truncating the faceIndex to always be zero. To catch mistakes like this in the future, I’ve added tests for this problem that should be run by our new 32-bit CI testing. 0e5ec18a

Dominik Honnef:

  • app: [Wayland] avoid stuck primary button when invoking window management. Clicking on the window border or the title bar initiates resizing and moving of the window respectively. This commit fixes a bug where this would cause a stuck pressed primary button, as we won’t receive a release event. The fix is to only update the set of pressed buttons after we’ve decided not to invoke window management. ad3db521
  • io/semantic: avoid unnecessary pointer indirection. Putting a string in an interface value has to (normally) heap allocate the string header and string. However, putting the address of a local string variable in an interface value has the same effect, as this causes the local variable to escape to the heap. b6e0376a
  • app: [Windows] include keyboard modifiers in move, drag, and scroll pointer events. cda73efa

Ilia Demianenko:

  • app: [Android] Set high refresh rate on startup. Some devices with high refresh rates limit SurfaceView apps to 60hz and need a specific API call to set it back. Same approach is used by The extra work is skipped on the devices that don’t need it. bcb123a0

Elias Naur:

  • app: really do deliver top-level key events. This is a fixup of 0dba85f52e5131c03d903c84355fb90cdb978811. See discussion at c0d3f67b


In x, I fixed a build error that crept in on the Windows build of notify and updated everything to the latest Gio APIs. Sebastien Binet improved the stroke package’s support for Arcs, and Gordon Klaus simplified the TextField component.

Chris Waldon:

  • go.*,component,styledtext,richtext,markdown: update font usage. This commit updates gio-x’s use of the text shaping API to use the new symbols defined in package instead of the old ones in It handles the breaking changes from 59c1ef3
  • notify,profiling,stroke: gofmt. 087d853
  • notify: fix windows build problem. This commit fixes an outdated field reference preventing this package from compiling properly on windows. e6edded

Sebastien Binet:

  • stroke: add ArcTo. dd57f5c
  • all: bump x/image@v0.7.0, x/sys@v0.7.0 and x/text@v0.9.0. 752f112

Gordon Klaus:

  • component: [API] remove redundant TextField.Alignment. b853331


I updated the examples to be compatible with the latest core and x APIs.

Chris Waldon:

  • go.*,component: update component TextField usage. This commit updates the component demo’s TextField page to use the correct alignment option after c78947f
  • go.*,bidi,fps-table,gophers,kitchen,markdown,textfeatures: update text usage. This commit updates references to font properties to use the symbols in instead of, handling the breaking change from 613640e
  • opengl: gofmt. b9983fa

I added Dominik Honnef’s excellent gotraceui project to our project showcase and added some notes on how to cross-compile Gio to the install page.

Chris Waldon:

  • content{,/doc/showcase{,/gotraceui}}: add gotraceui to showcase. I’ve added a showcase entry for gotraceui and swapped it for sprig on the main page as sprig isn’t as actively developed. I’ve also alphabetized the showcase page in order to avoid any concerns over unfairly prioritizing certain projects. 27968f8
  • content/doc/install: add notes about cross-compilation. This commit provides some hints about how to cross-compile Gio between desktop platforms. 1a54827


Thanks for reading!

Chris Waldon