|
| 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