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?
Setting the size
The system wants to know the size of the widget, but you didn't have enough information before. Now that you've measured the lines, though, you can calculate the size.
Add the following code to the _calculateWidth method in your VerticalParagraph class:
double sum = 0;
for (LineInfo line in _lines) {
sum += line.bounds.height;
}
_width = sum;
Why do I say to add the heights to get the width? Well, width
is a value that you expose to the outside world. Outside users are thinking of rotated (vertical) lines. The height
variable, on the other hand, is what you are using internally for the non-rotated (horizontal) lines.
The intrinsic height is how tall the widget would like to be if it had as much room as it wanted. Add the following code to the _calculateIntrinsicHeight method:
double sum = 0;
double maxRunWidth = 0;
for (TextRun run in _runs) {
final width = run.paragraph.maxIntrinsicWidth;
maxRunWidth = math.max(width, maxRunWidth);
sum += width;
}
// 1
_minIntrinsicHeight = maxRunWidth;
// 2
_maxIntrinsicHeight = sum;
The numbered comments are explained here:
- As before, height and width are mixed because of the rotation. You don't want any word to be clipped, so the minimum height the widget would like to be is the length of the longest run.
- If the widget laid everything out in one long vertical line, this is how tall it would like to be.
Add the following to the end of the _layout method:
print("width=$width height=$height");
print("min=$minIntrinsicHeight max=$maxIntrinsicHeight");
Restart the app. You should see something similar to this:
width=123.0 height=300.0
min=126.1953125 max=722.234375
The min and max intrinsic heights are what you would expect if the line were vertical:
Painting Text to the Canvas
You're almost done. All that's left is painting the runs. Copy the following code into the draw method:
canvas.save();
// 1
canvas.translate(offset.dx, offset.dy);
// 2
canvas.rotate(math.pi / 2);
for (LineInfo line in _lines) {
// 3
canvas.translate(0, -line.bounds.height);
// 4
double dx = 0;
for (int i = line.textRunStart; i < line.textRunEnd; i++) {
// 5
canvas.drawParagraph(_runs[i].paragraph, Offset(dx, 0));
dx += _runs[i].paragraph.longestLine;
}
}
canvas.restore();
Explaining the parts in order:
- Move to the start location.
- Rotate the canvas 90 degrees. The old top is now on the right.
- Move to where the line should start. The
y
value is negative so this moves up each new line, that is, to the right on the rotated canvas. - Draw each run (word) one at a time.
- The offset is the start location of the run on the line.
Here is an image showing the order of how the text runs are drawn in the three lines:
Run the app one more time.
Tadaa! The beautiful vertical script adds the perfect touch to our travel app.
Where to Go From Here?
You can download the completed project using the Download Materials button at the top or bottom of this tutorial.
If you've stuck it out this far, it shows that you're a hardy traveler in the world of text rendering. You've come a long way. But just like a casual tourist to a foreign culture, a short trip can only scratch the surface of what lies beneath. In this last section I will give you some guidance of where to travel next.
Suggested Improvements
There are many ways the vertical text widget could be improved to make it more generally usable. Here are a few:
- Handle new line characters.
- Support
TextSpan
trees with substring styling, differentiating aVerticalText
widget from aVerticalRichText
widget. - Add hit testing and semantics.
- Emojis and CJK characters should have the correct orientation.
- Research what it would take to make a vertical
TextField
with text selection and a blinking cursor.
I'm going to be working on these things in future. Feel free to watch my progress or participate here.
Further Study
Read the source code with its comments. I'm being serious. Start here:
For YouTube videos, I recommend:
And I found these articles to be especially good:
- The Engine architecture
- Flutter’s Rendering Engine: A Tutorial
- Everything you need to know about tree data structures
- Android’s Font Renderer
- Inside Flutter
I hope you've enjoyed this tutorial on text rendering in Flutter. I'd love to hear your comments or questions in the forum discussion below.