Skip to content

Instantly share code, notes, and snippets.

@mdebbar
Last active June 29, 2022 19:22
Show Gist options
  • Save mdebbar/93886d22a4cd40e050d1533d7e0016bf to your computer and use it in GitHub Desktop.
Save mdebbar/93886d22a4cd40e050d1533d7e0016bf to your computer and use it in GitHub Desktop.
High level description of how text layout is done in Flutter Web; and where we are falling short and need browsers' help with new APIs.

This is a high level overview of how we do text layout and rendering in Flutter Web:

Fragmenting

Before we start measuring text, we first need to break it down into fragments. A fragment is the longest substring in the paragraph that satisfies the following:

  1. Contains no soft or hard line breaks.
  2. The entire fragment has the same text direction.
  3. The entire fragment has the same text style.

1. Line breaks

In a multi-line paragraph, we need to know where soft/hard line breaks are so we can correctly split the paragraph into lines that fit within a certain width.

Currently, we don't have an API from the browser to do this, so we wrote our own implementation of the unicode line breaking algorithm. We have a script that takes unicode line break properties and embeds them into our dart code as an encoded string.

This implementation has been doing okay but it's not complete. There are 2 main issues:

  • Discrepancies: Chrome, for example, deviates from the line breaking rules listed in the unicode spec.
  • South East Asian languages: These require more advanced line break detection using dictionaries and morphological analysis.

This is where a proposal like Intl.Segmenter v2 can help. It'll give us more correct line break detection and improve our code size and performance. It'll also keep us in sync with how the browser performs line break detection.

2. Text direction

Fragments of text that have different directions have to be separated because they need to be positioned differently. There's no browser API to determine the direction of text, so we implemented something similar to the Google Closure library's LTR/RTL detection.

3. Text style

Fragments that have different styles need to be measured separately using the canvas2d measureText API as explained below.

Measurement

1. Width

After the text is broken down into fragments, we now measure the width of each fragment using canvas's measureText API. This allows us to position the fragments inside the paragraph, and determine which fragments go in each line.

2. Height

We use a hidden DOM element to measure the height of text. But because this is an expensive operation, we cache the results using a cache key that combines:

  • font-family
  • font-size
  • line-height

We assume that these are the only styles that affect the text's height.

Note: In some browsers, the measureText implementation provides information about the height of text too. We don't use this yet because not all browsers support it.

Rendering

In flutter web, when using the html renderer (as opposed to the Canvaskit renderer), we have two modes for displaying text:

1. DOM:

Simply create text <span>s for the fragments, then insert them into a <p> element. We also set the appropriate css styles on the <p> root element and all the <span> children.

2. Canvas2d:

Paint the text in a canvas2d context using the fillText/strokeText APIs.

These canvas2d text drawing APIs are limited, some things require custom code from us to implement:

Some things simply can't be done in canvas2d:

Queries

Our text implementation in Flutter Web allows the querying of certain information after the paragraph has been laid out. These are useful when dealing with text editing for example. Some of the queries that we support:

  • Highlighting a range of text: One may need to highlight a range of text, so they query about the bounding boxes that cover that range of text.
  • Placing the cursor: When the user clicks with a mouse inside a piece of text, the app needs to know where to place the cursor, so it can query about that.

Placeholders

We also support having placeholders inline inside the paragraph of text. Each placeholder box has a size (width & height) and a few properties to control its vertical alignment with the text around it.

Placeholders are taken into account when laying out the paragraph, and their positions within the paragraph can be queried too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment