HTML: Text, Eventually
Text was obvious. So naturally, it took a while
When we wrote about the rendering primitives in SolidRT,
we glossed over <text> a little.
It looked simple enough. But there was more to it.
And <span> was actually the last element to arrive in SolidRT.
Keeping It Simple
When we first started implementing it,
<text> really was just that: text.
As simple as it gets.
There was no italic or bold text within a paragraph,
no different colors, no clickable links. Just text. No <span>, yet.
We could get a paragraph of richer text by just using multiple <text> elements inside a <view> with flexbox, giving each one its own properties.
But that is not how paragraphs are usually done.
Keeping It Together
There are rules about how text flows, where lines break, how words are shaped and how different runs of text fit together. A paragraph renderer handles all of that as one piece of text, rather than as a collection of independent elements.
Our renderer is Flutter’s Impeller - and Impeller’s paragraph renderer can do exactly that.
So the obvious thing to do was to put some structure around those pieces of text and let Impeller handle the paragraph.
In HTML, we would have a <p> element with <span> elements inside it.
And that’s what we needed: a way to mark up different runs of text within a <text> element where we could set properties or attach handlers to.
This made the implementation straightforward.
<text> could contain spans, and the spans could provide the extra styling and behavior we needed, while Impeller continued to handle the actual paragraph layout.
But <span> is a bit of an oddball.
Most elements have their own layout and geometry.
A <span> doesn’t.
It contributes text to the paragraph, and the paragraph renderer determines where and how that text is laid out.
Shouldn’t it be named a <d-span> then?
No, it isn’t detached either - it belongs to the paragraph, and cannot be laid out independently.
So even though it may look like a layout-attached element, it isn’t.
That means it’s fine to use it inside a detached text element, <d-text>.
We could have stopped here.
Breaking It Up Again
Earlier this year, Cheng Lou, who worked on the React core team at Facebook, released Pretext.
Pretext is pretty awesome. It gives much more control over paragraph rendering, while being more efficient, too. And the good thing was, for us, that it would actually be easier to implement than in a browser.
Pretext measures words individually and then uses those measurements to lay out the paragraph. But every browser has its own rules for deciding where lines break, and text doesn’t always behave the same way when measured separately from when it is actually laid out and drawn. So that’s tricky for Pretext, which operates in browsers.
SolidRT has a much easier situation. We don’t have multiple browser engines to match. The same engine that measures the text is also responsible for laying it out and drawing it. That meant we could move the core of Pretext into the engine - into fast Rust.
Text is split and shaped natively, once, and JavaScript gets the metrics it needs to lay out the paragraph.
The <text> element can then use the same engine for rendering, while providing an extra interface for more control, Pretext-style.
Wrapping It Up
For <text>, usage remains simple.
It is still just a way to draw a paragraph like you’d expect, with <span> for styling and interaction within the text.
The difference is that paragraph rendering is not done anymore by Impeller.
Developers don’t have to know any of that. They just get faster text rendering.
And if you do want more control, the building blocks are right there.
prepareText() and layoutNextLine() can be imported directly from core, and used to build your own text flow.
There is an example of this in text-flow.tsx.
Which brings us back to where we started.
Text was one of the first things HTML gave us.
For SolidRT, it was the last piece to make its way into pixels.