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 4 of 4 of this article. Click here to view the first page.

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:

  1. 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.
  2. 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:

Intrinsic heights

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:

  1. Move to the start location.
  2. Rotate the canvas 90 degrees. The old top is now on the right.
  3. 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.
  4. Draw each run (word) one at a time.
  5. 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:

Order of text run drawing

Run the app one more time.

Tadaa! The beautiful vertical script adds the perfect touch to our travel app.

Final 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 a VerticalText widget from a VerticalRichText 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:

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.