• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1Shaped Text
2=============
3A series of object models for describing a low-level builder for multi-line formatted text, and the resulting objects that expose the results of shaping that text. These are done outside of DOM Text nodes, and outside of any particular rendering model (e.g. canvas2d or webgl).
4
5A related explainer focuses on suggested [extensions to canvas2d](text_c2d.md) to allow it to efficiently render the shaped results, and to offer helper objects for inspecting useful properties from a typeface.
6
7[Overview document](text_overview.md)
8
9## Target audience
10
11We want to target web apps that have already chosen to render their content either in canvas2d,
12or webgl, or in some other fashion, but still want access to the powerful international text shaping
13and layout services inherent in the browser. In the case of canvas2d, which already has some facilities
14for text, we want to address the missing services and low-level results needed for creating interactive
15text editing or high-perferformance rendering and animations.
16
17Rather than 'extend' the existing canvas2d fillText() method, we propose an explicit 2-step model:
18process the 'rich' text input into shaped results, and then expose those results to the client, allowing
19them to draw or edit or consume the results as they choose.
20
21JavaScript frameworks are another target audience. This proposal is heavily influenced by successful
22APIs on native platforms (desktop and mobile) and seeks to deliver similar control and performance.
23Thus it may be quite natural that sophiticated frameworks build upon these interfaces, providing more
24'friendly', constrained versions of the features. This is expected, since multiple 'high level' models
25for text are valid, each with its own opinions and tradeoffs. The goal of this API is to expose the
26core services and results, and leave the opinionated layers to the JavaScript community.
27
28### Principles
29* An imperative JavaScript-friendly text representation.
30* Restrict input to only what is needed for shaping and metrics is provided.
31* Decorations (i.e. colors, underlines, shadows, effects) are explicitly not specified, as those can
32vary widely with rendering technologies (and client imagination).
33
34## Sequence of calls
35
36For maximum re-use and efficiency, the process of going from rich-text description to final shaped
37and formated results is broken into stages. Each 'stage' carries out specific processing, and in-turn
38becomes a factory to return an instances of the next stage.
39
40`TextBuilder`, `ShapedText` and `FormattedText` objects are used in sequence:
41
42```js
43const builder = new ParagraphBuilder(font-fallback-chain);
44const shaped = builder.shape(DOMString text, sequence<TextBlock> blocks);
45const formatted = shaped.format(double width, double height, alignment);
46```
47
48A Block is a descriptor for a run of text. Currently there are two specializations, but others may be
49added without breaking the design.
50
51```WebIDL
52interface Typeface {
53    // Number or opaque object: Whatever is needed for the client to know exactly
54    // what font-resource (e.g. file, byte-array, etc.) is being used.
55    // Without this, the glyph IDs would be meaningless.
56    //
57    // This interface is really an “instance” of the font-resource. It includes
58    // any font-wide modifies that the client (or the shaper) may have requested:
59    //    e.g. variations, synthetic-bold, …
60    //
61    // Factories to create Typeface can be described elsewhere. The point here
62    // is that such a unique identifier exists for each font-asset-instance,
63    // and that they can be passed around (in/out of the browser), and compared
64    // to each other.
65};
66
67interface TextBlock {
68    unsigned long length;  // number of codepoints in this block
69};
70
71interface InFont {
72    attribute sequence<Typeface> typefaces; // for preferred fallback faces
73    attribute double size;
74    attribute double scaleX?;   // 1.0 if not specified
75    attribute double skewX?:    // 0.0 if not specified (for oblique)
76
77    attribute sequence<FontFeature> features?;
78    // additional attributes for letter spacing, etc.
79};
80
81interface FontBlock : TextBlock {
82    attribute InFont font;
83};
84
85interface PlaceholderBlock : TextBlock {
86    attribute double width;
87    attribute double height;
88    attribute double offsetFromBaseline;
89};
90
91interface ShapedTextBuilder {
92    constructor(TextDirection,          // default direction (e.g. R2L, L2R)
93                sequence<Typeface>?,    // optional shared fallback sequence (after TextBlock's)
94                ...);
95
96    ShapedText shape(DOMString text, sequence<TextBlock>);
97};
98```
99
100Here is a simple example, specifying 3 blocks for the text.
101
102```js
103const fontA = new Font({family-name: "Helvetica", size: 14});
104const fontB = new Font({family-name: "Times", size: 18});
105const blocks = [
106  { length: 6, font: fontA },
107  { length: 5, font: fontB },
108  { length: 6, font: fontA },
109];
110
111const shaped = builder.shape("Hello text world.", blocks);
112
113// now we can format the shaped text to get access to glyphs and positions.
114
115const formatted = shaped.format({width: 50, alignment: CENTER});
116```
117
118This is explicitly intended to be efficient, both for the browser to digest, and for the client to
119be able to reuse compound objects as they choose (i.e. reusing fontA in this example).
120
121If there is a mismatch between the length of the text string, and the sum of the blocks' lengths,
122then an exception is raised.
123
124## Access the results of shaping and formatting
125FormattedText has methods and the raw data results:
126
127```WebIDL
128typedef unsigned long TextIndex;
129
130interface TextPosition {
131    readonly attribute TextIndex textIndex;
132    readonly attribute unsigned long lineIndex;
133    readonly attribute unsigned long runIndex;
134    readonly attribute unsigned long glyphIndex;
135};
136
137interface FormattedText {
138    // Interaction methods
139
140    // Given a valid index into the text, adjust it for proper grapheme
141    // boundaries, and return the TextPosition.
142    TextPosition indexToPosition(TextIndex index);
143
144    // Given an x,y position, return the TextPosition
145    // (adjusted for proper grapheme boundaries).
146    TextPosition hitTextToPosition(double x, double y);
147
148    // Given two logical text indices (e.g. the start and end of a selection range),
149    // return the corresponding visual set of ranges (e.g. for highlighting).
150    sequence<TextPosition> indicesToVisualSelection(TextIndex t0, TextIndex t1);
151
152    // Raw data
153
154    readonly attribute Rect bounds;
155
156    readonly attribute sequence<TextLine> lines;
157};
158```
159
160The sequence of TextLines is really and array of arrays: each line containing an
161array of Runs (either Glyphs or Placeholders for now).
162
163```WebIDL
164// Shared by all output runs, specifying the range of code-points that produced
165// this run. Known subclasses: TextRun, PlaceholderRun.
166interface TextRun {
167    readonly attribute TextIndex startIndex;
168    readonly attribute TextIndex endIndex;
169};
170
171interface GlyphRunFont {
172    // Information to know which font-resource (typeface) to use,
173    // and at what transformation (size, etc.) to use it.
174    //
175    readonly attribute Typeface typeface;
176    readonly attribute double size;
177    readonly attribute double scaleX?;   // 1.0 if not specified
178    readonly attribute double skewX?:    // 0.0 if not specified (could be a bool)
179};
180
181interface GlyphRun : TextRun {
182    readonly attribute GlyphRunFont font;
183
184    // Information to know what positioned glyphs are in the run,
185    // and what the corresponding text offsets are for those glyphs.
186    // These “offsets” are not needed to correctly draw the glyphs, but are needed
187    // during selections and editing, to know the mapping back to the original text.
188    //
189    readonly attribute sequence<unsigned short> glyphs;     // N glyphs
190    readonly attribute sequence<float> positions;           // N+1 x,y pairs
191    readonly attribute sequence<TextIndex> indices;         // N+1 indices
192};
193
194interface PlaceholderRun : TextRun {
195    readonly attribute Rect bounds;
196};
197
198interface TextLine {
199    readonly attribute TextIndex startIndex;
200    readonly attribute TextIndex endIndex;
201
202    readonly attribute double top;
203    readonly attribute double bottom;
204    readonly attribute double baselineY;
205
206    readonly attribute sequence<TextRun> runs;
207};
208```
209
210With these data results (specifically glyphs and positions for specific Typeface objects)
211callers will have all they need to draw the results in any fasion they wish. The corresponding
212start/end text indices allows them to map each run back to the original text.
213
214This last point is fundamental to the design. It is recognized that a client creating richly
215annoated text will associate both shaping (e.g. Font) information, as well as arbitrary decoration
216and other annotations with each block of text. Returning in each Run the corresponding text range
217allows the client to "look-up" all of their bespoke additional information for that run (e.g.
218colors, shadows, underlines, placeholders, etc.). This frees the Browser from having to support
219or even understand the union of all possible decorations (obviously impossible).
220
221
222## Alternatives and Prior Art
223
224This model is designed to be low-level, to appeal to performance sensitive applications
225(e.g. animations) and sophisticated text (editors). It is also intended to feel 'natural' to a developer
226coming from a native app environment (desktop or mobile).
227
228We recognized that many (more casual) users may also want access to some of these services. That is
229appropriate, but we posit that with the right primitives and data exposed, such higher-level models
230can be constructed by the JavaScript community itself, either as formal Frameworks or as refined
231sample / example code.
232
233One excelent example of a higher-level data model is [Formatted Text](https://github.com/WICG/canvas-formatted-text/blob/main/explainer-datamodel.md) and we hope to explore ways to layer these
234two proposals, allowing high-level clients to utilize their data model, but still have the option
235to access our lower level accessors (as they wish).
236
237## Rendering in Canvas2D
238The [next explainer](text_c2d.md) describes how to take these results and render them
239into an (extended) Canvas context.
240
241## Contributors:
242 [mikerreed](https://github.com/mikerreed),
243