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
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 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
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
- 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 index
0in the string, that is, the letter “M” of “My text line.”
- The word boundary for some character offset in the string.
What we are not given:
- A way to get the number of lines.
- A way to get the actual text within the text boxes.
- A way to control how the text is laid out.
- A way to draw text on a path.
- A way to measure and draw short runs of text without building a whole paragraph.
- A way to get good font and text metrics (ascent, decent, top, bottom, leading).
Comparison to Android and iOS
In Android, although most people would use a TextView, you are given the low level control to do all the things I listed above by using the StaticLayout, Canvas, and Paint classes. Here are a just few of the many options available:
I don’t have as much low level text drawing experience on iOS (because I thought I would just learn to do everything in Flutter), but Core Text has a rich set of tools.
The Flutter repo style guide stays:
It is common for SDKs that target multiple platforms … to provide APIs that work on all their target platforms. Unfortunately, this usually means that features that are unique to one platform or another are unavailable.
For Flutter, we want to avoid this by explicitly aiming to be the best way to develop for each platform individually. Our ability to be used cross- platform is secondary to our ability to be used on each platform. (emphasis added)
Currently, for those of us who need to do low level text rendering in our apps, Flutter is not the best platform to develop on.
You may say that Flutter already provides the Text and RichText widgets. That’s true and they are very good. They will meet the needs of 99.9% of developers. But there are use cases for accessing lower level text rendering tools.
My use case is laying out and rendering traditional Mongolian text, which is written vertically and line wraps from left to right. English is written sideways, but CJK and emoji characters should retain their normal orientation.
There are a few “solutions” using widget composition, but when you add the need for text styling (like “underlining” by drawing a vertical line to the right of the text, for example), a much more robust solution would be to do all the text measuring, layout, and painting by hand.
Chinese, Japanese, and Korean can also be laid out in various vertical orientations. Like Mongolian, there are workarounds for one off situations, but for common usage, a rendering package would be more helpful. Read this for a more detailed description of the requirements.
Flutter only supports supports right-to-left and left-to-right layouts. Vertical layouts aren’t (and won’t be 😞) supported. I don’t fault the Flutter team for that. It’s a lot of work. But I wish they would have given us more tools to do it ourselves.
Apps that do text art would also benefit from a low level access to the text painting tools.
Filling non-rectangular shapes with text or flowing the text around exclusion areas are related use cases.
I’m not trying to talk anyone out of using Flutter. I still like it a lot. I don’t ever want to go back to building the same app multiple times for different platforms.
In writing this article I’m kind of hoping someone will say, “No, you’re wrong. If you do such and such, then you’ll have access to the low level text rendering tools.” I‘m not holding out too much hope for that, though, since one of the main Flutter developers commented this:
If you want “real” vertical text, with emphasis marks and ruby and inline horizontal bidi text and everything, then the best I can offer is that you could try to write a package to support this using what poor primitives we have provided, but it probably won’t be a satisfactory experience.
What I’m really hoping is that the Flutter team will give us the same freedom with text that we already have at the UI layout level. It wouldn’t be especially hard to add a
dart:ui class exposing more of the LibTxt library. True, it would be more to maintain, and developers might occasionally use it to write inefficient code, but the freedom is worth the cost in my opinion. Make Flutter the best way to develop for any platform in any language.
Shortly after I published this article, Gary Qian, one of the main contributors to LibTxt, and Ian Hickson, one of the original co-creators of Flutter, both responded in the comments below. This gives me hope that some of the problems I described above might be resolved in the future. At the very least, I’m impressed with how approachable the Flutter team is.
I opened a GitHub issue for this. If you also need to do custom text layout, please give it a thumbs up. The number of upvotes is one way the Flutter team determines what to work on next.