Newsletter, December 2022
Text Selection and RSS

First up, I’d like to thank Egon Elbre for implementing an RSS feed for these newsletters. You can find that feed here if you prefer to consume content via feeds.

This past month saw the merge of several massive change to our text shaping API and widgets. Firstly, bidirectional text and font fallback are now fully supported, though appropriate fonts must be loaded into the text shaper. Secondly, widget.Label can now optionally have state that makes the text selectable. Let’s talk about these big changes in a little more detail:

The merge of bidirectional text support changed the text shaper API in several important (and breaking) ways:

  • Code that used a text.Shaper will need to be changed to accept a *text.Shaper. That type changed from an interface to a concrete type exposed by package text.
  • Shaping text uses the text.(*Shaper).Layout method as always, but the input parameter providing the text has changed from an io.RuneReader to an io.Reader. This enables higher-performance text shaping.
  • The text.Line, text.Layout, and text.GlyphCluster types have been dropped from the exported API. These codified structural details about the shaped text that became extremely complex with the introduction of bidirectional text. Instead, the text shaper now implements an iterator that can be used to read a stream of text.Glyph, each of which contains information about where it should be positioned and how many runes it represents. For widgets that directly invoke the text shaper, processing this stream of glyphs can determine the dimensions of lines and other information that was once conveyed by text.Line. You can see an example of this processing in the implementation used by package widget.

The font fallback feature means that displaying text containing runes form multiple fonts (emoji and a latin text font, for example) is now possible. As long as any of the loaded fonts can display a glyph for an input rune, that glyph will be shown. Fonts are searched for glyph candidates in the order in which they are loaded. Note that only monochrome emoji fonts (such as this one) can be displayed by Gio’s current font rendering.

As for text selection, this was a major refactoring effort that extracted the guts of the code powering the text editor’s implementation into a standalone type (the currently-unexported widget.textView) and rebuilt widget.Editor atop it. At the same time, the new widget.Selectable type was added, which can hold state for selectable text. You can make an existing widget.Label selectable by populating the Selectable field. If you instead use the material.LabelStyle API to display text, you can provide selectable state by populating the State field.

Text selection is powered by a new API available on widget.Editor and widget.Selectable. You can invoked their Regions()(editor,selectable) method with a rune range of interest, and they will return to you a slice of positioned rectangles covering the glyphs corresponding to those runes. This enables the implementation of widgets that decorate text or respond to mouse input on particular text (spelling correction, hyperlinks, etc…). This method will also handle all of the weird nuances of bidirectional text (which is why it returns a slice of multiple rectangles).

My work on all three of these major text enhancements was financially supported by Plato Team, for which I couldn’t be more grateful. I would also like to thank Elias for reviewing some truly monster-sized patchsets and for the thoughtful feedback that created our current API. Dominik Honnef also deserves a shoutout for some excellent points about the text API design. Thanks all!

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.

Changes by repo

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

core gio

Core saw the text changes mentioned above, some path drawing optimizations from Egon, and some macOS platform bugfixes from Elias.

Breaking Changes by Author

Chris Waldon:

  • font/{gofont,opentype},text,widget{,/material}: [API] add font fallback and bidi support. This commit restructures the entire text shaping stack to enable lines of shaped text to have non-homogeneous properties like which font face they belong to and which direction a segment of text is going. b7d126e2
  • text,widget: [API] drop runereader based shaping API. The io.Reader based API has the potential to be significantly more efficient, and there are very few users of the runereader API. This commit simply drops it entirely in favor of the reader API. aa2a948b

Non-breaking Changes by Author

Chris Waldon:

  • widget: unify text painting and fix premature termination. This commit unifies all widget text painting to use a single function and fixes two bugs that could result in visible glyphs failing to be painted. 719278bb
  • go.*,text: implement shaper-driven line truncation. This commit pushes limiting the maximum number of lines of text into the shaper implementation. This is more efficient than doing it in widgets, and also opens the door for future use of the shaper to insert ellipsis and other truncating characters as appropriate. 2db1a7bf
  • text,widget: numerous bugfixes and enhancements spread across several commits. bfb47538 b0483975 5b40d3cd 12da7182 fe5878bc c455f0f3
  • widget: add ReadOnly mode to editor. This commit provides a new ReadOnly boolean on the editor. If set, the editor functions as a selectable label. User interaction cannot change the contents of the editor (though application code can still use the API). 0b456579
  • text: consume io.Reader in shaper. io.Reader is actually a more efficient interface than io.RuneReader, as we can pull bytes out and check for cache hits without doing redundant rune<->string conversions. This isn’t implemented yet, however. 5d6cc289
  • widget: create standalone textView. This commit adds a standalone state type for manipulating and displaying text. It reads text from a minimal interface, shapes it, tracks valid cursor positions, and provides sizing and scrolling services to higher-level widgets. My long term goal with these types is to export them to allow non-core widgets to build atop them, but I’ve left them private for now. f99aff96
  • widget{,/material}: rebuild label and editor with textView. This commit rebuilds the editor and label types on the common foundation provided by textView. This enables labels to have optional state that makes them selectable, and allows the two widgets to share the code for managing cursor positions, displaying selections, and soforth. Labels now have an additional Layout function which can be invoked if they have a Selectable. It accepts a layout.Widget used to paint their contents. Stateless labels should still use the old Layout method. e98c8955
  • widget: expose text region resolution. This commit adds exported methods to both LabelState and Editor allowing callers to locate the text regions representing a range of runes. This can be used to build interactive subregions of text, like (for instance) hyperlinks. dc6fbf07

Elias Naur:

  • .builds: bump to Go 1.18.9. eccc94dc
  • app: [macOS] properly handle middle mouse button up event. 98f098f5
  • app: [macOS] defer Window destroy to after window close. The windowWillClose callback is too soon to destroy our Window: at least draw callbacks may be called after windowWillClose but before the window is gone. This change moves cleanup to the viewDidMoveToWindow callback where we’re sure the NSView is no longer active. 51325012
  • text,widget: use != for flag tests. 5d1d1df2
  • gpu/internal/opengl: avoid UNPACK_ROW_LENGTH/PACK_ROW_LENGTH on GLES2. Similarly to WebGL1, they’re not supported in OpenGL ES 2.0. 1a84517b
  • gpu/internal/opengl: don’t query FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING on GLES2. It’s not supported in OpenGL ES 2. f8221bb2

Egon Elbre:

  • unit: add PxToDp and PxToSp. PxToDp and PxToSp are useful when you are trying to calculate text-size or widget size based on dynamically sized container. e9bce02b
  • widget: fix build for go1.17. go.mod specifies 1.18, due to go.mod behavior and to avoid some issues with updating the dependencies. However, we can still support older go version, as long as it compiles with the older version. c81a1f96
  • gpu: optimize encodeQuadTo. 8bc6737d
  • gpu: optimize pack.tryAdd. 827e20d8

gio-x

X saw the addition of support for triangular caps in stroked paths, as well as a bugfix for zero-length paths. Additionally, X was updated to be compatible with the shaper changes mentioned above.

Chris Waldon:

  • styledtext,richtext: adapt to new text shaping APIs. This commit restructures the internals of styledtext to be compatible with Gio’s new text shaping API. Unfortunately, styledtext isn’t completely bidi-safe. Spans containing bidi text should display correctly, but if a style change occurs within runs of text that move against the primary text direction, the runs will be displayed out of order. The only good way to fix this is to enable Gio core to provide a richer styled text API, which we should be able to do pretty soon. a2b41ad
  • deps: update to latest gio. 6b4a6cc
  • ci: update to Go 1.18.9. e2d994f

Lothar May:

gio-example

Example saw the addition of a selectable heading in the kitchen, some bidi text examples, and an improvement to the multiwindow example.

Chris Waldon:

  • kitchen: add example selectable text. 971c3a6
  • bidi: add example with bidi text. This commit adds a simple example with an editor pre-populated with bidirectional text. It serves as an example both of how to load extra fonts, font fallback, and how bidirectional text functions in the editor. 847dd7c
  • deps: update to latest gio and gio-x. fa09300

Egon Elbre:

giouiorg

As mentioned above, Egon implemented an RSS feed for the site, while I mostly just tried to keep CI working and wrote newsletters.

Egon Elbre:

  • site: add rss and summary. 8f9e71d
  • content/news: add summary. 2117c3e
  • site/rss: add published date. e9d3e13
  • site/rss: preserve pubDate time. 1b03ff7
  • content/doc: document layout.Spacer. 8fb571a
  • content/doc: add wasm example. dbc3cb7

Chris Waldon:

  • content/news: add november newsletter. df7eb5f
  • cmd/giouiorg: update go runtime version to 1.18. d70a1af
  • cmd/giouiorg: update go runtime version to 1.19. 4655d36
  • content/news: update publication dates. 649cd13