Flutter Text Rendering

Learn about how Flutter renders Text widgets and see how to make your own custom text widget. By Jonathan Sande.

5 (18) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Stepping Down: Text Rendering Objects

You’ve seen diagrams of the Flutter architecture like this one:

Flutter Architecture

What you did in the last section was at the Widgets layer. In this section you are going to step down into the Rendering, Painting, and Foundation layers. Even though you’re going deeper, things are actually simpler at the lower levels of the Flutter framework because there aren’t multiple trees to deal with.

Are you still at the breakpoint that you added? Command-click RenderParagraph to see what’s inside.

Take a few minutes to scroll up and down the RenderParagraph class. Here are a few things to watch out for:

  • RenderParagraph extends RenderBox. That means this render object is rectangular in shape and has some intrinsic width and height based on the content. For a render paragraph, the content is the text.
  • It handles hit testing. Hey, kids, no hitting each other! If you are going to hit something, hit RenderParagraph. It can take it.
  • The performLayout and paint methods are also interesting.

Did you notice that RenderParagraph hands off its text painting work to something called TextPainter? Find the definition of _textPainter near the top of the class. Let’s leave the Rendering layer and go down to the Painting layer. Command-click TextPainter.

Take a minute to view the scenery.

  • There is an important member variable called _paragraph of type ui.Paragraph. The ui part is a common way to prefix classes that are from the dart:ui library, the very lowest level of the Flutter framework.
  • The layout method is really interesting. You can’t instantiate Paragraph directly. You have to use a ParagraphBuilder class to do it. It takes a default paragraph style that applies to the whole paragraph. This can be further modified with styles that are included in the TextSpan tree. Calling TextSpan.build() adds those styles to the ParagraphBuilder object.
  • You can see that the paint method is pretty simple here. TextPainter just hands the paragraph off to canvas.drawParagraph(). If you Control-click that, you’ll see that it calls paragraph._paint.

You’ve come to the Foundation layer of the Flutter framework. From within the TextPainter class, Control-click the following two classes:

  • ParagraphBuilder: It adds text and pushes and pops styles, but the actual work is handed off to the native layer.
  • Paragraph: Not much to see here. Everything is handed down to the native layer.

Go ahead and stop the running app now.

Here is a diagram to summarize what you saw above:

Low level text rendering

Way Down: Flutter’s Text Engine

It can be a little scary leaving your homeland and going to a place where you can’t speak the native language. But it’s also adventurous. You’re going to leave the land of Dart and go visit the native text engine. They speak C and C++ down there. The good thing is that there are a lot of signs in English.

You can’t Command-click anymore in your IDE, but the code is all on GitHub as a part of the Flutter repository. The text engine is called LibTxt. Go there now at this link.

We’re not going to spend a long time here, but if you like exploring, have a look around the src folder later. For now, though, let’s all go to the native class that Paragraph.dart passed its work off to: txt/paragraph_txt.cc. Click that link.

You may enjoy checking out the Layout and Paint methods in your free time, but for now scroll down just a little and take a look at the imports:

#include "flutter/fml/logging.h"
#include "font_collection.h"
#include "font_skia.h"
#include "minikin/FontLanguageListCache.h"
#include "minikin/GraphemeBreak.h"
#include "minikin/HbFontCache.h"
#include "minikin/LayoutUtils.h"
#include "minikin/LineBreaker.h"
#include "minikin/MinikinFont.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkFontMetrics.h"
#include "third_party/skia/include/core/SkMaskFilter.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "third_party/skia/include/effects/SkDashPathEffect.h"
#include "third_party/skia/include/effects/SkDiscretePathEffect.h"
#include "unicode/ubidi.h"
#include "unicode/utf16.h"

From this you can learn (with a little digging) how LibTxt does its work. It is based on a number of other libraries. Here are a few interesting tidbits:

  • Minikin does things like measuring and laying out the text.
  • ICU helps Minikin with things like breaking text into lines.
  • HarfBuzz helps Minikin with choosing the right glyph shapes from a font.
  • Skia paints the text and text decorations on a canvas.

The more you look around, the more you realize how much is involved in correctly rendering text. I didn’t even have time to mention issues like interline spacing, grapheme clusters and bidi text.

You’ve journeyed way down into to the framework and text rendering engine. Now it’s time to step back up and put that knowledge to use.

Stepping Up Your Game: Building a Custom Text Widget

You’re going to do something now that you’ve probably never done before. You’re going to create a custom text widget, not by composition as you normally would, but by making a render object that draws text using the lowest levels of Flutter that are available to you.

Flutter wasn’t originally designed to allow developers to do custom text layout, but the Flutter team is responsive and willing to make changes. Keep an eye on this GitHub issue for progress updates on that.

The Steppe Up travel app is looking OK so far, but it would be nice to support the Mongolian script. Traditional Mongolian is unique. It’s written vertically. The standard Flutter text widgets support a horizontal layout, but we need a vertical layout where the lines wrap from right to left.

Traditional Mongolian

Custom Render Object

In order to focus on the low level text layout, I’ve included the widget, render object, and helper classes in the starter project.

Starter project classes

Let me briefly explain what I did in case you want to make a different custom render object in the future.

  • vertical_text.dart: This is the VerticalText widget. I made it by starting with the RichText source code. I stripped almost everything out and changed it to LeafRenderObjectWidget, which has no children. It creates a RenderVerticalText object.
  • render_vertical_text.dart: I made this by stripping RenderParagraph way down and swapping the width and height measurements. It uses VerticalTextPainter instead of TextPainter.
  • vertical_text_painter.dart: I started with TextPainter and took out everything that I didn’t need. I also exchanged the width and height calculations and removed support for complex styling with TextSpan trees.
  • vertical_paragraph_constraints.dart: I used height for the constraint instead of width.
  • vertical_paragraph_builder.dart: I started with ParagraphBuilder, removed everything I didn’t need, added default styling and made the build method return VerticalParagraph instead of Paragraph.
  • line_breaker.dart: This is a meant to be a substitute for the Minikin LineBreaker class, which is not exposed in Dart.

In the following sections you’ll finish making the VerticalParagraph class by measuring the words, laying them out in lines, and painting them to the canvas.