@textmode/document 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,236 @@
1
+ # Visual guide to document format 2.0
2
+
3
+ > Versioned v2 reference. Current v3 documentation is in the
4
+ > [v3 specification](specification-3.0.md) and [visual guide](schema-3.0.md).
5
+ > The current decoder validates these older shapes and migrates them to v3.
6
+
7
+ These diagrams show the structure of `textmode.document@2.0.0`. For the complete
8
+ rules, see the [v2 specification](specification-2.0.md) and [JSON Schema](../schemas/2.0/schema.json).
9
+ For decoding examples and error handling, see the [consumer guide](consumer-guide.md).
10
+
11
+ ## Document shapes
12
+
13
+ A v2 document has one of two shapes. Both include the format, version, and
14
+ `canvas` extent; metadata is optional. The `target` field selects the shape.
15
+
16
+ ```mermaid
17
+ flowchart TD
18
+ Root["Root object"] --> Format["format = textmode.document<br/>required"]
19
+ Root --> Version["formatVersion = 2.0.0<br/>required"]
20
+ Root --> Target{"target<br/>required; oneOf"}
21
+ Root --> Canvas["canvas: pixel extent<br/>required in both shapes"]
22
+ Root -. "optional" .-> Metadata["metadata"]
23
+
24
+ Target -->|selected| Selected["Selected snapshot"]
25
+ Selected --> SelectedGrid["grid: dimensions + cell metrics"]
26
+ Selected --> SelectedLayer["layer: id + cells"]
27
+
28
+ Target -->|all| All["Layer stack"]
29
+ All --> Layers["layers: one or more stack layers"]
30
+ ```
31
+
32
+ ### Root fields
33
+
34
+ | Field | Required | Meaning |
35
+ | --------------- | ------------- | ------------------------------------------------------------------------------------ |
36
+ | `format` | Yes | Must be `textmode.document`. |
37
+ | `formatVersion` | Yes | Must be `2.0.0`. This file-format version is independent of the npm package version. |
38
+ | `target` | Yes | Must be `selected` or `all`; selects the matching shape. |
39
+ | `canvas` | Yes | Legacy pixel extent. It is not necessarily the final or composite viewport. |
40
+ | `grid` | Selected only | Grid metrics shared by the selected snapshot's cells. |
41
+ | `layer` | Selected only | One selected cell snapshot with an informational ID. |
42
+ | `layers` | All only | Nonempty ordered stack of layers, bottom first. |
43
+ | `metadata` | No | Creation timestamp and producer identity. If present, both members are required. |
44
+
45
+ Ordinary objects allow unknown fields. The decoder omits them from canonical
46
+ output, so decoding and reserializing does not preserve them.
47
+
48
+ ## Selected snapshot and layer stack
49
+
50
+ The selected shape stores one cell snapshot. It has no layer visibility,
51
+ opacity, blend mode, offsets, rotation, or font identity. The `all` shape stores
52
+ one or more independently measured layers and preserves their stack order.
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ subgraph Selected["target: selected"]
57
+ SCanvas["canvas<br/>width, height in pixels"]
58
+ SGrid["grid<br/>cols, rows<br/>cellWidth, cellHeight"]
59
+ SLayer["layer<br/>id + cells"]
60
+ SGrid --> SLayer
61
+ SCanvas -. "width = cols × cellWidth<br/>height = rows × cellHeight" .-> SGrid
62
+ end
63
+
64
+ subgraph All["target: all"]
65
+ ACanvas["canvas<br/>first layer's pixel extent"]
66
+ Stack["layers[]<br/>one or more; bottom to top"]
67
+ L0["first stack layer"]
68
+ L1["next layer"]
69
+ More["zero or more further layers"]
70
+ ACanvas -. "legacy extent follows first layer grid" .-> L0
71
+ Stack --> L0 --> L1 --> More
72
+ end
73
+ ```
74
+
75
+ For a selected document, `canvas.width` and `canvas.height` equal the grid's
76
+ pixel extent. For a stack document, root `canvas` follows the **first layer's**
77
+ grid. It does not describe the combined bounds, placement, or clipping of the
78
+ stack. Every stack layer has its own grid, cell metrics, and cells.
79
+
80
+ | Object | Required fields | Local constraints |
81
+ | ---------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
82
+ | `canvas` | `width`, `height` | Positive safe integers in pixels. |
83
+ | `grid` | `cols`, `rows`, `cellWidth`, `cellHeight` | Positive safe integers. Pixel products must remain safe integers; the decoder checks the extent relationship. |
84
+ | Selected `layer` | `id`, `cells` | `id` is a nonempty string. |
85
+ | Cell collection | `encoding`, `rows` | `encoding` is exactly `object-rows-v1`; `rows` is an array. |
86
+
87
+ In a selected document, `grid` is at the root. In a stack, each layer has a
88
+ grid and the root `canvas` is checked against the first layer's grid.
89
+
90
+ Each stack layer requires all of the following fields:
91
+
92
+ | Field | Constraint or meaning |
93
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
94
+ | `id` | Nonempty, informational source ID. It is not a persistent identity and need not be unique. |
95
+ | `visible` | Boolean presentation state. Hidden layer cells remain in the document. |
96
+ | `opacity` | Number from `0` to `1`, separate from cell alpha. |
97
+ | `blendMode` | Nonempty mode name. The decoder preserves names it does not recognize; consumers decide what they can render. |
98
+ | `offsetX`, `offsetY` | Pixel offsets. In the textmode.js compositor, positive x moves right and positive y moves down. |
99
+ | `rotationZ` | Layer rotation in degrees. In textmode.js it is clockwise around the rectangular layer center; the decoder preserves it. |
100
+ | `grid` | This layer's columns, rows, and cell pixel dimensions. |
101
+ | `cells` | This layer's complete dense cell data. |
102
+
103
+ The textmode.js exporter writes `normal`, `additive`, `multiply`, `screen`,
104
+ `subtract`, `darken`, `lighten`, `overlay`, `softLight`, `hardLight`,
105
+ `colorDodge`, `colorBurn`, `difference`, and `exclusion`. These are producer
106
+ values, not an exhaustive enum in the schema.
107
+
108
+ ## Grid and dense cell storage
109
+
110
+ Both shapes store cells in `object-rows-v1` encoding. The row and column arrays
111
+ are dense and row-major. Every grid position has a cell, including cells with
112
+ transparent foreground and background colors.
113
+
114
+ ```mermaid
115
+ flowchart TD
116
+ Grid["grid<br/>cols × rows"] --> Enc["cells.encoding<br/>object-rows-v1"]
117
+ Enc --> Rows["cells.rows[]<br/>row-major"]
118
+ Rows --> Row["rows[y]"]
119
+ Row --> Cell["rows[y][x]"]
120
+ Cell --> Coord["cell.x = x<br/>cell.y = y"]
121
+ Grid -. "rows.length = rows" .-> Rows
122
+ Grid -. "each row length = cols" .-> Row
123
+ Grid -. "all coordinates are present" .-> Cell
124
+ ```
125
+
126
+ Coordinates are zero-based. Each cell's `x` and `y` must match its position in
127
+ `rows[y][x]`. Consumers must reject a mismatch instead of reordering or repairing
128
+ cells.
129
+
130
+ | Cell field | Required | Schema-level shape |
131
+ | -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
132
+ | `x`, `y` | Yes | Nonnegative safe-range integers; decoder requires them to match array positions. |
133
+ | `character` | Yes | String constrained by `minLength: 1` and `maxLength: 64`; decoder rejects unpaired surrogates and counts Unicode scalar values against its configured limit. |
134
+ | `foreground`, `background` | Yes | Each is a six/eight digit hex color or an RGBA object. |
135
+ | `transform` | Yes | Object with `invert`, `flipX`, `flipY`, and `rotation`. |
136
+
137
+ An RGBA color object requires integer `r`, `g`, `b`, and `a` channels. Each
138
+ channel is between `0` and `255`. The schema permits additional properties in
139
+ these objects, as it does for the other ordinary objects in the format.
140
+
141
+ The decoder checks row sizes, coordinates, and canvas dimensions against the
142
+ grid. The schema alone does not check these relationships.
143
+
144
+ ## Cell, color, and transform structure
145
+
146
+ Each cell requires coordinates, a glyph string, foreground and background
147
+ colors, and all four transform values.
148
+
149
+ ```mermaid
150
+ flowchart TD
151
+ Cell["Cell"] --> X["x: zero-based column"]
152
+ Cell --> Y["y: zero-based row"]
153
+ Cell --> Character["character: nonempty Unicode string"]
154
+ Cell --> Foreground["foreground"]
155
+ Cell --> Background["background"]
156
+ Cell --> Transform["transform"]
157
+
158
+ Foreground --> Color["Color"]
159
+ Background --> Color
160
+ Color --> Hex6["#RRGGBB<br/>alpha defaults to 255"]
161
+ Color --> Hex8["#RRGGBBAA"]
162
+ Color --> RGBA["RGBA object"]
163
+ RGBA --> Channels["r, g, b, a<br/>integers 0–255"]
164
+
165
+ Transform --> Invert["invert: boolean"]
166
+ Transform --> FlipX["flipX: boolean"]
167
+ Transform --> FlipY["flipY: boolean"]
168
+ Transform --> Rotation["rotation: finite degrees<br/>canonical range [0, 360)"]
169
+ ```
170
+
171
+ The decoder converts colors to RGBA objects and normalizes cell rotation to
172
+ `[0, 360)`. It preserves Unicode strings without normalization. A glyph can
173
+ contain multiple scalar values, such as a combining sequence or joined emoji;
174
+ unpaired surrogates are invalid. The decoder also applies a scalar-count limit.
175
+
176
+ Cell alpha and layer opacity are separate. A cell is visually empty only when
177
+ both foreground and background alpha are zero. A space glyph with a visible
178
+ background remains meaningful. Inversion cannot generally be implemented by
179
+ swapping colors when alpha is present.
180
+
181
+ ## Metadata
182
+
183
+ Metadata is optional at the root. If the `metadata` object exists, it requires
184
+ both `createdAt` and `generator`. The generator requires a nonempty `name` and
185
+ `version`.
186
+
187
+ ```mermaid
188
+ flowchart LR
189
+ Root["Document"] -. "optional" .-> Metadata["metadata"]
190
+ Metadata --> CreatedAt["createdAt<br/>date-time string"]
191
+ Metadata --> Generator["generator"]
192
+ Generator --> Name["name: nonempty string"]
193
+ Generator --> Version["version: nonempty string"]
194
+ ```
195
+
196
+ `createdAt` uses the format's timestamp profile: uppercase `T`, seconds,
197
+ timezone, optional fractional seconds, and no leap seconds. Validators must
198
+ enable date-time format assertions to validate the `date-time` format keyword.
199
+ Metadata is informational. Consumers must not use it to choose executable
200
+ behavior, fetch resources, or bypass validation.
201
+
202
+ ## Validation and decoding boundaries
203
+
204
+ The schema describes structure and local constraints. The specification defines
205
+ field meaning and relationships. The decoder checks documents, applies limits,
206
+ and migrates supported older formats to canonical v3 output.
207
+
208
+ ```mermaid
209
+ flowchart LR
210
+ Input["v2 or supported legacy JSON"] --> Decoder["Decoder<br/>validation, limits,<br/>migration, normalization"]
211
+ Decoder --> Canonical["Canonical v3 document<br/>with migration warning"]
212
+ Decoder --> Error["Failure result"]
213
+ Schema["JSON Schema<br/>structure and local constraints"] -. "v2 structure" .-> Input
214
+ Spec["Format specification<br/>meaning and relationships"] -. "v2 rules" .-> Decoder
215
+ ```
216
+
217
+ A schema-valid document can still fail decoding. For example, the schema does
218
+ not check row sizes against the grid or enforce a caller's total cell budget.
219
+ See the [consumer guide](consumer-guide.md#use-json-schema) for validator setup
220
+ and the [v2 specification](specification-2.0.md#safety-limits) for decoder limits.
221
+
222
+ ## Scope and compatibility
223
+
224
+ Version 2 stores editable cell snapshots and layer stacks. It does not store
225
+ font identity or font assets, editor project state, authored animation,
226
+ post-processing, or final rendered pixels. Importers apply their own font,
227
+ feature-support, and placement policies.
228
+
229
+ The current decoder migrates v2 documents and supported `textmode.layer` files
230
+ to canonical v3 layers. It reports a migration warning and can be configured to
231
+ reject legacy `textmode.layer` input. The v2 JSON Schema describes the v2 wire
232
+ shapes; it is not a schema for the legacy formats or current v3 output.
233
+
234
+ For the exact normative rules, including unknown properties, timestamp
235
+ requirements, blend-mode behavior, and safety guidance, see the
236
+ [Format 2.0 specification](specification-2.0.md).
@@ -0,0 +1,117 @@
1
+ # Visual guide to document format 3.0
2
+
3
+ These diagrams show the structure of `textmode.document@3.0.0`. For the complete
4
+ rules, see the [specification](specification-3.0.md) and [JSON Schema](../schemas/3.0/schema.json).
5
+ The [consumer guide](consumer-guide.md) includes complete example documents.
6
+
7
+ ## One shape for any layer count
8
+
9
+ ```mermaid
10
+ flowchart TD
11
+ Document["Document object"] --> Format["format = textmode.document"]
12
+ Document --> Version["formatVersion = 3.0.0"]
13
+ Document --> Layers["layers[]: at least one; bottom to top"]
14
+ Document -. optional .-> Metadata["metadata: createdAt + generator"]
15
+ Layers --> Layer["Same complete layer structure for every entry"]
16
+ Layer --> Identity["id: informational string"]
17
+ Layer --> Presentation["visible, opacity, blendMode<br/>offsetX, offsetY, rotationZ"]
18
+ Layer --> Grid["grid: cols, rows, cellWidth, cellHeight"]
19
+ Layer --> Cells["cells: encoding + dense rows"]
20
+ ```
21
+
22
+ All shown fields are required except metadata. A document with one layer has one
23
+ array entry. Root `target`, `layer`, `grid`, and `canvas` fields are rejected.
24
+ Other unknown fields are accepted and omitted from canonical output. IDs need not
25
+ be unique. The format does not define a font, viewport, or animation.
26
+
27
+ ## Grids and storage
28
+
29
+ ```mermaid
30
+ flowchart TD
31
+ Grid["Layer grid: positive safe integers"] --> Extent["Derived pixel extent<br/>cols × cellWidth; rows × cellHeight"]
32
+ Grid --> Encoding["cells.encoding = object-rows-v1"]
33
+ Encoding --> Rows["cells.rows: grid.rows rows"]
34
+ Rows --> Row["rows[y]: grid.cols cells"]
35
+ Row --> Cell["rows[y][x]"]
36
+ Cell --> Coordinates["cell.x = x; cell.y = y<br/>zero-based, redundant checks"]
37
+ Grid -. "decoder checks safe products and array agreement" .-> Coordinates
38
+ ```
39
+
40
+ Every grid position is stored, including transparent cells. Layers can have
41
+ different dimensions and cell sizes. The decoder checks row sizes and coordinates
42
+ against each layer's grid.
43
+
44
+ ## Layer presentation
45
+
46
+ ```mermaid
47
+ flowchart LR
48
+ Layer["Layer"] --> Visible["visible: boolean"]
49
+ Layer --> Opacity["opacity: 0 to 1"]
50
+ Layer --> Blend["blendMode: nonempty name"]
51
+ Layer --> Offset["offsetX / offsetY: pixels<br/>positive right / down"]
52
+ Layer --> Rotation["rotationZ: finite degrees<br/>clockwise about center in textmode.js"]
53
+ Opacity --> Alpha["Multiplies layer appearance<br/>separate from cell alpha"]
54
+ Blend --> Support["Unknown names preserved<br/>consumer checks support"]
55
+ ```
56
+
57
+ Hidden layers keep their cells. Layer rotation is preserved without canonical
58
+ normalization. Stack order is bottom first. Every presentation field is explicit;
59
+ JSON Schema defaults are not used to supply missing values.
60
+
61
+ ## Complete cell payload
62
+
63
+ ```mermaid
64
+ flowchart TD
65
+ Cell["Cell: all fields required"] --> XY["x / y: nonnegative safe integers"]
66
+ Cell --> Glyph["character: nonempty Unicode glyph string"]
67
+ Cell --> FG["foreground"]
68
+ Cell --> BG["background"]
69
+ FG --> Color{"Color representation"}
70
+ BG --> Color
71
+ Color --> RGB["#RRGGBB: alpha 255"]
72
+ Color --> RGBAHex["#RRGGBBAA"]
73
+ Color --> Channels["Object: integer r, g, b, a<br/>each 0 to 255"]
74
+ Cell --> Transform["transform"]
75
+ Transform --> Booleans["invert / flipX / flipY: booleans"]
76
+ Transform --> Rotation["rotation: finite degrees<br/>canonical [0, 360)"]
77
+ ```
78
+
79
+ Canonical colors use RGBA objects. Unicode is preserved without normalization;
80
+ unpaired surrogates fail. The schema's minLength/maxLength are 1/64; decoder
81
+ scalar-count budgets are configurable. A visible background on a space matters.
82
+ Only cells whose foreground and background alpha are both zero are visually
83
+ empty. Inversion cannot generally be replaced by swapping colors.
84
+
85
+ ## Metadata
86
+
87
+ ```mermaid
88
+ flowchart LR
89
+ Document["Document"] -. optional .-> Metadata["metadata"]
90
+ Metadata --> Date["createdAt: real date-time<br/>uppercase T + timezone + seconds"]
91
+ Metadata --> Generator["generator"]
92
+ Generator --> Name["name: nonempty producer string"]
93
+ Generator --> Version["version: nonempty producer string"]
94
+ ```
95
+
96
+ All children are required when metadata is present. Fractional seconds are
97
+ optional; leap seconds are unsupported. Enable date-time format assertions in
98
+ validators. Metadata is informational and never authorizes code or resource loads.
99
+
100
+ ## Readers and canonical output
101
+
102
+ ```mermaid
103
+ flowchart TD
104
+ V3["v3: unified layers"] --> Decoder["Decode known fields<br/>enforce budgets + cross-field rules"]
105
+ V2["v2: selected or all<br/>original validation including canvas"] --> Migration["Convert structure<br/>neutral presentation for selected snapshots"]
106
+ V1["Legacy textmode.layer v1<br/>when acceptLegacy is true"] --> Migration
107
+ Migration --> Decoder
108
+ Decoder --> Success["Success: canonical v3 layers<br/>RGBA colors; normalized cell rotation"]
109
+ Decoder --> Failure["Failure: code, message, optional path"]
110
+ Success --> Warnings["Migration warnings for older input"]
111
+ ```
112
+
113
+ The decoder checks documents directly; a separate schema validation pass is
114
+ optional. It omits unknown fields from canonical output. Applications must check
115
+ input size before parsing and apply their own font, feature, and placement rules.
116
+ See the [specification](specification-3.0.md#validation-budgets-and-compatibility)
117
+ for default limits and the [v2 visual guide](schema-2.0.md) for older shapes.
@@ -0,0 +1,294 @@
1
+ # Textmode document format 2.0
2
+
3
+ > Versioned v2 reference. Current v3 documentation is in the
4
+ > [v3 specification](specification-3.0.md) and [visual guide](schema-3.0.md).
5
+ > The current decoder validates these older shapes and migrates them to v3.
6
+
7
+ ## Status
8
+
9
+ This specification defines `textmode.document@2.0.0`. Its [JSON Schema](../schemas/2.0/schema.json)
10
+ is also available as `@textmode/document/schema/2.0`. The schema's `$id` is an
11
+ identifier and does not guarantee a hosted download. Package and document versions
12
+ are independent.
13
+
14
+ Version 2 stores editable cell data. It does not store final rendered pixels,
15
+ post-processing, an editor project, or authored animation. For complete examples
16
+ of the current format, see the [consumer guide](consumer-guide.md).
17
+
18
+ ## Encoding
19
+
20
+ A textmode document is a UTF-8 JSON object. Producers should follow the I-JSON
21
+ profile in RFC 7493:
22
+
23
+ - object member names are unique;
24
+ - integers remain within the interoperable safe range;
25
+ - non-finite numbers are not emitted;
26
+ - timestamps use an uppercase, timezone-qualified RFC 3339 representation.
27
+
28
+ The JavaScript parser uses `JSON.parse`, which keeps the last value for a
29
+ repeated key. It does not report duplicate keys.
30
+
31
+ The media type is `application/json`. `.textmode.json` is a suggested filename
32
+ suffix; existing exporter downloads use `.json` by default. Extensions and
33
+ media types are discovery hints; consumers must route a decoded object using
34
+ its `format` member.
35
+
36
+ ## Root discriminator
37
+
38
+ Every v2 document contains:
39
+
40
+ ```json
41
+ {
42
+ "format": "textmode.document",
43
+ "formatVersion": "2.0.0",
44
+ "target": "selected"
45
+ }
46
+ ```
47
+
48
+ `target` is an on-disk shape discriminator:
49
+
50
+ - `selected` contains one cell snapshot in `layer`;
51
+ - `all` contains a layer stack in `layers`.
52
+
53
+ It is not an instruction to an exporter and must not be extended with temporal
54
+ values such as `animation`. Authored animation requires a distinct document or
55
+ container format.
56
+
57
+ ## Version compatibility
58
+
59
+ The version uses a SemVer-shaped identifier. Version 2 does not define reader
60
+ negotiation or a formal extension mechanism. Consumers must accept only explicitly
61
+ tested versions and migrations; an unknown `2.x` version is not automatically
62
+ compatible.
63
+
64
+ For a supported version, consumers ignore unknown ordinary fields. The decoder
65
+ omits them from canonical output, so reserialization does not preserve them.
66
+ Consumers reject unknown `format`, `target`, and `encoding` values because those
67
+ values change how to read the document.
68
+
69
+ Version 2 does not define `extensionsUsed`, `extensionsRequired`, extension
70
+ namespacing, or an extension registry. A later revision must define those
71
+ rules before claiming extension compatibility.
72
+
73
+ ## Selected cell snapshot
74
+
75
+ The `selected` shape contains:
76
+
77
+ - legacy grid pixel extent in `canvas`;
78
+ - logical grid dimensions and cell metrics in `grid`;
79
+ - an informational source ID and complete cell rows in `layer`.
80
+
81
+ This shape is a cell snapshot, not a lossless layer document. It does not carry
82
+ visibility, opacity, blend mode, offsets, layer rotation, or font identity.
83
+
84
+ ## Layer stack
85
+
86
+ The `all` shape contains one or more layers. Each layer includes:
87
+
88
+ - informational ID;
89
+ - visibility and opacity;
90
+ - blend mode name;
91
+ - x/y offsets and z rotation;
92
+ - logical grid and cell metrics;
93
+ - complete cell rows.
94
+
95
+ The order of `layers` is bottom-to-top stack order. The exporter emits the base
96
+ layer first, followed by user layers. There is no fixed format-level layer count;
97
+ the decoder enforces the configurable budget.
98
+
99
+ `id` is informational source identity, scoped to the source snapshot. It is not a
100
+ persistent identity across files. The v2 decoder does not require IDs to be unique;
101
+ consumers must not infer reference semantics from them.
102
+
103
+ Offsets are compositing offsets in pixels. In the textmode.js compositor, positive
104
+ x moves right and positive y moves down. `rotationZ` is clockwise degrees about
105
+ the layer's rectangular center; the center remains the rotation origin when an
106
+ offset is applied. The decoder preserves layer rotation without normalization.
107
+ Opacity is from 0 through 1 and multiplies the layer presentation independently
108
+ of cell alpha. Invisible layers retain their cell data.
109
+
110
+ The textmode.js exporter writes `normal`, `additive`, `multiply`, `screen`,
111
+ `subtract`, `darken`, `lighten`, `overlay`, `softLight`, `hardLight`, `colorDodge`,
112
+ `colorBurn`, `difference`, or `exclusion`. The wire schema and decoder accept any
113
+ nonempty bounded blend-mode name. Consumers must decide whether they support it
114
+ before presentation; unknown names are not silently converted to `normal`.
115
+
116
+ ## Canvas field
117
+
118
+ The v2 field named `canvas` is the grid pixel extent, not necessarily the HTML
119
+ canvas or final output surface. Its values obey:
120
+
121
+ ```text
122
+ canvas.width = grid.cols × grid.cellWidth
123
+ canvas.height = grid.rows × grid.cellHeight
124
+ ```
125
+
126
+ For an `all` document, the root extent is derived from the first layer's grid.
127
+ Consumers must not infer composite placement or clipping from this field.
128
+
129
+ The meaning of this field must not change within version 2.
130
+
131
+ ## Cell encoding
132
+
133
+ Version 2 defines one encoding:
134
+
135
+ ```json
136
+ {
137
+ "encoding": "object-rows-v1",
138
+ "rows": []
139
+ }
140
+ ```
141
+
142
+ The encoding is dense and row-major:
143
+
144
+ - `rows.length` equals `grid.rows`;
145
+ - every row length equals `grid.cols`;
146
+ - a cell's `x` equals its array index within the row;
147
+ - a cell's `y` equals the row index;
148
+ - every grid coordinate is present, including transparent cells.
149
+
150
+ Consumers reject coordinate disagreement rather than silently repairing or
151
+ reordering cells.
152
+
153
+ ## Glyph identity
154
+
155
+ `character` contains a non-empty, bounded, well-formed Unicode glyph string.
156
+ Vector fonts commonly use one Unicode scalar, while tileset maps may use an
157
+ extended grapheme cluster containing combining marks, variation selectors, or
158
+ zero-width joiners. Unpaired surrogates are invalid. The default codec budget
159
+ is 64 Unicode scalar values per glyph string.
160
+
161
+ No Unicode normalization is implied. Consumers preserve the string exactly and
162
+ apply product-specific representability rules after protocol decoding.
163
+
164
+ ## Colors
165
+
166
+ Foreground and background colors use either:
167
+
168
+ - `#RRGGBB`, whose alpha is implicitly 255;
169
+ - `#RRGGBBAA`;
170
+ - `{ "r": 0, "g": 0, "b": 0, "a": 255 }` with integer channels from
171
+ 0 through 255.
172
+
173
+ The canonical decoder normalizes colors to RGBA channel objects.
174
+
175
+ A cell is visually empty only when both foreground and background alpha are
176
+ zero. A space with a visible background remains meaningful cell data.
177
+
178
+ ## Transforms
179
+
180
+ Every cell contains:
181
+
182
+ - `invert`: boolean;
183
+ - `flipX`: boolean;
184
+ - `flipY`: boolean;
185
+ - `rotation`: finite degrees.
186
+
187
+ The canonical decoder normalizes rotation into `[0, 360)`. It therefore
188
+ normalizes both `360` and `-360` to `0` and `-90` to `270`.
189
+
190
+ Consumers must reject unsupported meaningful transforms instead of
191
+ approximating them. In particular, swapping foreground and background is not a
192
+ general replacement for inversion when alpha is present.
193
+
194
+ ## Metadata
195
+
196
+ Metadata is optional and informational:
197
+
198
+ ```json
199
+ {
200
+ "createdAt": "2026-08-28T12:00:00.000Z",
201
+ "generator": {
202
+ "name": "textmode.export.js",
203
+ "version": "1.8.1"
204
+ }
205
+ }
206
+ ```
207
+
208
+ `generator.name` identifies the producer and is not restricted to
209
+ `textmode.export.js`. If metadata is present, `createdAt` must be a real
210
+ calendar date and time in the uppercase, timezone-qualified RFC 3339 profile:
211
+ seconds are required, fractional seconds are optional, and leap seconds are
212
+ not supported in version 2. The decoder preserves valid timestamps as written.
213
+
214
+ Consumers must not use metadata to select executable behavior, fetch resources,
215
+ or bypass document validation.
216
+
217
+ ## Safety limits
218
+
219
+ The shared codec enforces structural safety limits and accepts caller-supplied
220
+ budgets. Every override must be a positive safe integer; invalid configuration
221
+ throws `TypeError`. Invalid document data instead returns a typed failure.
222
+
223
+ | Limit | Default | Counting rule |
224
+ | ------------------------- | ---------- | ---------------------------------------------------------- |
225
+ | `maxCells` | 16,777,216 | Cumulative cells across the document. |
226
+ | `maxLayers` | 4,096 | Layers in an `all` document; selected has one snapshot. |
227
+ | `maxLayerIdLength` | 1,024 | UTF-16 code units per identifier. |
228
+ | `maxCharacterLength` | 64 | Unicode scalar values per glyph string. |
229
+ | `maxMetadataStringLength` | 4,096 | UTF-16 code units per metadata string and blend-mode name. |
230
+
231
+ These budgets apply after JSON parsing and do not limit file bytes or parser
232
+ memory. The schema's glyph `maxLength` remains 64 even when an app raises the
233
+ decoder limit. Applications can also enforce their own feature and size limits.
234
+
235
+ Consumers should:
236
+
237
+ - check file bytes before reading;
238
+ - cap total layers and total decoded cells;
239
+ - use checked multiplication for grid dimensions;
240
+ - bound identifiers and metadata strings;
241
+ - parse large files away from latency-sensitive UI work;
242
+ - abort work when the owning import session is cancelled.
243
+
244
+ Documents never authorize URL fetching, font installation, or code execution.
245
+
246
+ ## Legacy migration
247
+
248
+ The compatibility decoder recognizes:
249
+
250
+ - `textmode.layer@1.0.0` as a selected cell snapshot;
251
+ - `textmode.layer@1.1.0` as a layer stack.
252
+
253
+ The current decoder migrates both to canonical v3 output and returns
254
+ `LEGACY_DOCUMENT_MIGRATED`. Set `acceptLegacy: false` to reject these legacy files;
255
+ this does not disable v2 document support.
256
+
257
+ ## JSON Schema and runtime decoder
258
+
259
+ The specification defines meaning and cross-field rules. The JSON Schema describes
260
+ structure for general tooling. The decoder also checks or applies:
261
+
262
+ - cross-field row and grid invariants;
263
+ - grid pixel extent equality;
264
+ - total resource budgets;
265
+ - well-formed and bounded glyph-string validation;
266
+ - rotation and color normalization;
267
+ - legacy migration;
268
+ - stable error codes and JSON paths.
269
+
270
+ ## Required fields
271
+
272
+ | Object | Required fields | Optional fields |
273
+ | --------------- | ------------------------------------------------------------------------------------------- | --------------- |
274
+ | Selected root | `format`, `formatVersion`, `target`, `canvas`, `grid`, `layer` | `metadata` |
275
+ | All root | `format`, `formatVersion`, `target`, `canvas`, `layers` | `metadata` |
276
+ | Canvas | `width`, `height` | None |
277
+ | Grid | `cols`, `rows`, `cellWidth`, `cellHeight` | None |
278
+ | Selected layer | `id`, `cells` | None |
279
+ | Stack layer | `id`, `visible`, `opacity`, `blendMode`, `offsetX`, `offsetY`, `rotationZ`, `grid`, `cells` | None |
280
+ | Cell collection | `encoding`, `rows` | None |
281
+ | Cell | `x`, `y`, `character`, `foreground`, `background`, `transform` | None |
282
+ | Transform | `invert`, `flipX`, `flipY`, `rotation` | None |
283
+ | Metadata | `createdAt`, `generator` | None |
284
+ | Generator | `name`, `version` | None |
285
+
286
+ Unknown ordinary members are allowed in all these objects but omitted by the
287
+ canonical decoder. All schema references are local. Timestamp validation needs
288
+ `date-time` format assertions enabled in the selected schema validator. The
289
+ schema alone does not enforce every decoder rule or configurable budget.
290
+
291
+ Version 2 has no font identity or embedded font assets. Glyph strings and cell
292
+ metrics cannot identify an exact font. It also has no editor history, project
293
+ preferences, authored frame timeline, or post-processing representation. An
294
+ importer must apply its own font and supported-feature policy.