Skip to content

Commit c0bbe6c

Browse files
committed
Add a document explaining the basics of the TextContent model
1 parent 3608b30 commit c0bbe6c

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

docs/content-model.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Text Content Model
2+
3+
In order to support many use cases securely, Terminal::Widgets has a somewhat
4+
complex content model _internally_, with simpler interfaces built on those
5+
internals that the app developer can use confidently.
6+
7+
8+
## Layering and Conversions
9+
10+
To support all the different desired use cases, Terminal::Widgets uses a
11+
hierarchy of text content types, each convertible to the next. Here's an
12+
example of the process, starting from a `TranslatableString`:
13+
14+
```
15+
TranslatableString — 'It is not ${c:important|diagnosis}.'
16+
17+
18+
MarkupString — 'Itway isway otnay ${c:important|diagnosis}.'
19+
20+
21+
SpanTree — SpanTree(StringSpan('Itway isway otnay '),
22+
│ InterpolantSpan(var => 'diagnosis',
23+
│ class => 'important'),
24+
│ StringSpan('.'))
25+
26+
Array[StringSpan] — [StringSpan('Itway isway otnay '),
27+
│ StringSpan('ibblestray',
28+
│ attributes => %(:important, :interpolation)),
29+
│ StringSpan('.')]
30+
31+
Array[RenderSpan] — [RenderSpan('', 'Itway isway otnay '),
32+
│ RenderSpan('bold red', 'ibblestray'),
33+
│ RenderSpan('', '.')]
34+
35+
Str — 'Itway isway otnay ibblestray.'
36+
```
37+
38+
Here's what the conversion pipeline looks like under the covers:
39+
40+
```
41+
TranslatableString — Highest level, opaque (though often in source language)
42+
43+
│ .translate ⚙️ Look up translated variant that matches interpolant vars
44+
45+
MarkupString — Includes inline semantic markup of spans and interpolants
46+
47+
│ .parse ⚙️ Parse markup into a tree of typed spans
48+
49+
SpanTree — Tree of SemanticSpans (InterpolantSpan or StringSpan)
50+
51+
│ .flatten ⚙️ Flatten tree into single list of SemanticSpans
52+
53+
│ .interpolate ⚙️ Interpolate variables into InterpolantSpans
54+
55+
Array[StringSpan] — Flattened (and if necessary interpolated) renderable spans
56+
57+
│ .render ⚙️ Render (StringSpan + attributes) into (RenderSpan + color)
58+
59+
Array[RenderSpan] — Flat array of RenderSpans for Widget.draw-line-spans
60+
61+
│ plain-text() ⚙️ (OPTIONAL) Join text from RenderSpans into a plain Str
62+
63+
Str — Just a plain string, for use where color doesn't matter
64+
```
65+
66+
67+
## Performance
68+
69+
While every attempt has been made to make individual operations efficient, it's
70+
obvious that a long pipeline will build up overhead, and some operations may be
71+
slow enough to be prohibitive when dealing with high volumes of text content.
72+
It is rarely necessary however to repeat the early stages of the pipeline on
73+
every screen refresh. Translations for strings that don't contain any
74+
interpolations will generally be static per language for a given software
75+
release, for example.
76+
77+
Thus `ContentRenderer` and its subclass `TranslatableContentRenderer` provide
78+
convenience methods that will drive rendering starting at any point in the
79+
render pipeline and ending at any point farther along. This both improves
80+
testability/debuggability, and allows caching of partially-completed rendering
81+
work.
82+
83+
84+
## Security
85+
86+
It is a critical design feature that the render pipeline is **one way**. This
87+
prevents a number of security and correctness bugs that would be caused by for
88+
instance accidentally parsing markup within the results of a variable
89+
interpolation.
90+
91+
Furthermore `TranslatableString` and `MarkupString` both default to NOT
92+
allowing generation of InterpolantSpans, so string contents that only
93+
coincidentally contain variable interpolation markup pose no threat. The
94+
programmer must explicitly mark a string as interpolatable _out of band_
95+
to turn this functionality on.
96+
97+
All of the semantic classes (those other than `Str` and `RenderSpan`) throw a
98+
special exception `X::CannotStringify` if an attempt is made to stringify them
99+
without going through the proper stages of the conversion pipeline.

0 commit comments

Comments
 (0)