@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.
- package/LICENSE +111 -0
- package/README.md +61 -2
- package/dist/constants.d.ts +29 -0
- package/dist/constants.js +29 -0
- package/dist/decode-shapes.d.ts +6 -0
- package/dist/decode-shapes.js +136 -0
- package/dist/decode.d.ts +24 -0
- package/dist/decode.js +92 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/parse.d.ts +12 -0
- package/dist/parse.js +24 -0
- package/dist/types/canonical.d.ts +16 -0
- package/dist/types/canonical.js +1 -0
- package/dist/types/decode.d.ts +39 -0
- package/dist/types/decode.js +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +1 -0
- package/dist/types/legacy.d.ts +26 -0
- package/dist/types/legacy.js +1 -0
- package/dist/types/v2.d.ts +34 -0
- package/dist/types/v2.js +1 -0
- package/dist/types/wire.d.ts +68 -0
- package/dist/types/wire.js +1 -0
- package/dist/validation.d.ts +37 -0
- package/dist/validation.js +205 -0
- package/docs/consumer-guide.md +230 -0
- package/docs/schema-2.0.md +236 -0
- package/docs/schema-3.0.md +117 -0
- package/docs/specification-2.0.md +294 -0
- package/docs/specification-3.0.md +134 -0
- package/package.json +86 -5
- package/schemas/2.0/schema.d.ts +3 -0
- package/schemas/2.0/schema.json +357 -0
- package/schemas/3.0/schema.d.ts +3 -0
- package/schemas/3.0/schema.json +267 -0
|
@@ -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.
|