I like Flutter. I like developing once and having the code run on both Android and iOS. I like how much time that saves. I like being a web developer now without having done any extra work. I like hot reload. I like building the UI quickly by composing widgets into layouts. I like how much simpler it is to make a ListView. I like state management. (Ok, just kidding about that one. But I can deal with it.) I like Dart. I like how how much easier Futures and async
/await
are than Android AsyncTasks and even iOS Dispatch Queues.
But after spending the last two weeks researching how Flutter renders text, I’m disappointed with the tools that we’ve been given.
We’ve been told:
Flutter’s layered architecture gives you control over every pixel on the screen.
That apparently doesn’t apply to the pixels used to paint text.
Low level text capabilities in Flutter
Flutter renders text using a combination of Skia, Harfbuzz, Minikin, and ICU using a library called LibTxt. Developers use it indirectly when they use a Text
widget or TextSpan
or even a TextPainter
. At the lowest level we have available to us, that is, dart:iu
, there is the Paragraph
class, which is built using a ParagraphBuilder
. These classes are basically just wrappers for the underlying LibTxt engine. Almost all the work is done by this engine with very little exposed in dart:ui
.
The Paragraph
class gives us the following control:
- Size: I can get the width and the height of the whole rendered paragraph, which might be a single line or multi-line.
- Distance to the baseline (for the first line only)
- Whether the text overflowed the
maxLines
variable. - The size and relative position of boxes of text runs (that is, lines or sections of bidi text). Here is an example:
- The text character index closest to some pixel location. In the example above, pixel
(1, 1)
would correspond to index0
in the string, that is, the letter “M” of “My text line.”