Flutter Text Rendering
Learn about how Flutter renders Text widgets and see how to make your own custom text widget. By Jonathan Sande.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Flutter Text Rendering
30 mins
- Getting Started
- A Journey Through the Framework
- Stepping in: The Text Widget
- Stepping Down: Text Rendering Objects
- Way Down: Flutter’s Text Engine
- Stepping Up Your Game: Building a Custom Text Widget
- Custom Render Object
- Calculating and Measuring Text Runs
- Laying Out Runs in Lines
- Setting the size
- Painting Text to the Canvas
- Where to Go From Here?
Stepping Down: Text Rendering Objects
You’ve seen diagrams of the Flutter architecture like this one:
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
extendsRenderBox
. 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
andpaint
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 typeui.Paragraph
. Theui
part is a common way to prefix classes that are from thedart:ui
library, the very lowest level of the Flutter framework. - The
layout
method is really interesting. You can’t instantiateParagraph
directly. You have to use aParagraphBuilder
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 theTextSpan
tree. CallingTextSpan.build()
adds those styles to theParagraphBuilder
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:
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.
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.
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 theRichText
source code. I stripped almost everything out and changed it toLeafRenderObjectWidget
, which has no children. It creates aRenderVerticalText
object. -
render_vertical_text.dart: I made this by stripping
RenderParagraph
way down and swapping the width and height measurements. It usesVerticalTextPainter
instead ofTextPainter
. -
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 withTextSpan
trees. -
vertical_paragraph_constraints.dart: I used
height
for the constraint instead ofwidth
. -
vertical_paragraph_builder.dart: I started with ParagraphBuilder, removed everything I didn’t need, added default styling and made the
build
method returnVerticalParagraph
instead ofParagraph
. -
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.