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