@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,134 @@
|
|
|
1
|
+
# Textmode document format 3.0
|
|
2
|
+
|
|
3
|
+
## Status and scope
|
|
4
|
+
|
|
5
|
+
This specification defines `textmode.document@3.0.0`. The npm package version
|
|
6
|
+
is independent of the document version. The [JSON Schema](../schemas/3.0/schema.json)
|
|
7
|
+
uses Draft 2020-12 and is available as `@textmode/document/schema/3.0`. Its `$id`
|
|
8
|
+
is an identifier and does not guarantee a hosted download.
|
|
9
|
+
|
|
10
|
+
A document stores editable cells in ordered layers. It does not store font identity
|
|
11
|
+
or assets, a viewport, animation, editor project state, history, or post-processing.
|
|
12
|
+
|
|
13
|
+
## Encoding and root
|
|
14
|
+
|
|
15
|
+
Files are UTF-8 JSON objects. Producers should use unique keys, safe integers,
|
|
16
|
+
finite numbers, and uppercase, timezone-qualified timestamps. JavaScript's
|
|
17
|
+
`JSON.parse` keeps the last value when a key is repeated; it does not report
|
|
18
|
+
duplicates. The `.textmode.json` suffix and `application/json` MIME type are hints.
|
|
19
|
+
Readers identify the format using `format` and `formatVersion`.
|
|
20
|
+
|
|
21
|
+
The root requires `format: "textmode.document"`, `formatVersion: "3.0.0"`, and
|
|
22
|
+
`layers`. Optional `metadata` is defined below. `layers` is a nonempty array in
|
|
23
|
+
bottom-to-top order; there is no separate single-layer shape. Root `target`,
|
|
24
|
+
`layer`, `grid`, and `canvas` are obsolete and invalid, including when null.
|
|
25
|
+
|
|
26
|
+
Unknown ordinary fields are allowed, except for the reserved root fields above.
|
|
27
|
+
The decoder omits unknown fields from its canonical output, so decoding and
|
|
28
|
+
reserializing does not preserve extensions. Readers reject unknown `format`,
|
|
29
|
+
`formatVersion`, and cell `encoding` values. Accept only explicitly supported
|
|
30
|
+
versions: v3 does not define version negotiation, required extensions, an
|
|
31
|
+
extension registry, or automatic compatibility with future 3.x versions.
|
|
32
|
+
|
|
33
|
+
## Layer structure and presentation
|
|
34
|
+
|
|
35
|
+
Every layer requires all fields below, including when it is the only layer:
|
|
36
|
+
|
|
37
|
+
| Field | Meaning |
|
|
38
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `id` | Nonempty informational source ID; not a cross-file identity. Uniqueness is not enforced. |
|
|
40
|
+
| `visible` | Boolean; hidden cells remain stored. |
|
|
41
|
+
| `opacity` | Number in [0, 1], independent of cell alpha. |
|
|
42
|
+
| `blendMode` | Nonempty bounded name; unknown names are preserved. Apps choose which names they support. |
|
|
43
|
+
| `offsetX`, `offsetY` | Finite pixel offsets; positive x is right and positive y is down in textmode.js. |
|
|
44
|
+
| `rotationZ` | Finite degrees, clockwise about the layer's rectangular center in textmode.js. Offset does not change the local rotation origin. Preserved without normalization. |
|
|
45
|
+
| `grid` | Independent logical dimensions and cell pixel metrics. |
|
|
46
|
+
| `cells` | Complete dense row-major cell data. |
|
|
47
|
+
|
|
48
|
+
The textmode.js producer names are `normal`, `additive`, `multiply`, `screen`,
|
|
49
|
+
`subtract`, `darken`, `lighten`, `overlay`, `softLight`, `hardLight`,
|
|
50
|
+
`colorDodge`, `colorBurn`, `difference`, and `exclusion`. These are not an enum
|
|
51
|
+
in the portable schema. Apps must not silently replace an unsupported meaningful
|
|
52
|
+
mode with `normal`.
|
|
53
|
+
|
|
54
|
+
Each `grid` requires positive safe integers for `cols`, `rows`, `cellWidth`, and
|
|
55
|
+
`cellHeight`. The cell count and pixel products must also be safe integers. Layers
|
|
56
|
+
can have different grids. A layer's pixel extent is `cols × cellWidth` by
|
|
57
|
+
`rows × cellHeight`. Do not infer a root grid or viewport.
|
|
58
|
+
|
|
59
|
+
## Cells and coordinates
|
|
60
|
+
|
|
61
|
+
Every `cells` object requires `encoding: "object-rows-v1"` and `rows`.
|
|
62
|
+
`rows.length` equals `grid.rows`; each row length equals `grid.cols`. Every cell,
|
|
63
|
+
including transparent cells, is present. A cell requires `x`, `y`, `character`,
|
|
64
|
+
`foreground`, `background`, and `transform`.
|
|
65
|
+
|
|
66
|
+
Coordinates are zero-based nonnegative safe integers. In `rows[y][x]`, the
|
|
67
|
+
cell's stored `x` and `y` must match its array position. Consumers must reject a
|
|
68
|
+
mismatch without repairing or reordering cells. The decoder enforces this rule.
|
|
69
|
+
|
|
70
|
+
## Glyphs, colors, and transforms
|
|
71
|
+
|
|
72
|
+
`character` is a nonempty, well-formed Unicode glyph string. It may contain
|
|
73
|
+
multiple scalar values, such as a combining sequence or joined emoji. Preserve it
|
|
74
|
+
without Unicode normalization. Unpaired surrogates are invalid. The schema uses
|
|
75
|
+
`minLength: 1` and `maxLength: 64`; the default decoder limit is 64 Unicode scalar
|
|
76
|
+
values. Changing a decoder limit does not change the published schema.
|
|
77
|
+
|
|
78
|
+
Each color is `#RRGGBB`, `#RRGGBBAA`, or an object with required integer `r`, `g`,
|
|
79
|
+
`b`, and `a` channels in [0, 255]. Six-digit notation implies alpha 255. Channels
|
|
80
|
+
are unpremultiplied; alpha 0 is transparent and 255 is opaque. Canonical output
|
|
81
|
+
uses RGBA channel objects. A space with visible background remains meaningful;
|
|
82
|
+
a cell is visually empty only when both foreground and background alpha are zero.
|
|
83
|
+
|
|
84
|
+
Each transform requires boolean `invert`, `flipX`, and `flipY` values and a finite
|
|
85
|
+
`rotation` in degrees. The decoder normalizes cell rotation to [0, 360): -90
|
|
86
|
+
becomes 270 and 360 becomes 0. Layer rotation is separate. Swapping colors is
|
|
87
|
+
not a general replacement for inversion when alpha is present. Consumers must
|
|
88
|
+
reject unsupported meaningful transforms rather than approximate them without an
|
|
89
|
+
explicit product operation.
|
|
90
|
+
|
|
91
|
+
## Metadata
|
|
92
|
+
|
|
93
|
+
Optional root `metadata`, when present, requires `createdAt` and `generator`.
|
|
94
|
+
`generator` requires nonempty bounded `name` and `version` strings and can
|
|
95
|
+
identify any producer. It does not select executable behavior.
|
|
96
|
+
|
|
97
|
+
`createdAt` is a real calendar date and time with uppercase T, required seconds,
|
|
98
|
+
a timezone, optional fractional seconds, and no leap seconds. Valid timestamps
|
|
99
|
+
are preserved as written. Enable date-time format assertions in JSON Schema
|
|
100
|
+
validators; format annotations alone are insufficient.
|
|
101
|
+
|
|
102
|
+
## Validation, budgets, and compatibility
|
|
103
|
+
|
|
104
|
+
The schema defines structure and local constraints. This specification defines
|
|
105
|
+
meaning. The decoder also checks row sizes, coordinates, safe products, Unicode,
|
|
106
|
+
and caller limits, and normalizes output. A schema-valid document may still fail
|
|
107
|
+
decoding.
|
|
108
|
+
|
|
109
|
+
| Decoder limit | Default | Counting rule |
|
|
110
|
+
| ------------------------- | ---------: | ---------------------------------------------------------- |
|
|
111
|
+
| `maxCells` | 16,777,216 | Cumulative across all layers. |
|
|
112
|
+
| `maxLayers` | 4,096 | Layer count. |
|
|
113
|
+
| `maxLayerIdLength` | 1,024 | UTF-16 code units per ID. |
|
|
114
|
+
| `maxCharacterLength` | 64 | Unicode scalar values per glyph. |
|
|
115
|
+
| `maxMetadataStringLength` | 4,096 | UTF-16 code units per metadata string and blend-mode name. |
|
|
116
|
+
|
|
117
|
+
Every override must be a positive safe integer. Invalid configuration throws
|
|
118
|
+
`TypeError`; invalid document input returns a failure result. Budgets apply
|
|
119
|
+
after parsing and do not bound input bytes or parser memory. Apps must apply
|
|
120
|
+
appropriate byte caps before reading/parsing and their own font and feature
|
|
121
|
+
policy after protocol decoding. Documents do not authorize fetching resources,
|
|
122
|
+
font installation, or code execution.
|
|
123
|
+
|
|
124
|
+
Supported v2 files are validated under their original rules, including canvas
|
|
125
|
+
extent checks, before conversion. Selected v2 snapshots move their root grid
|
|
126
|
+
into one layer and receive visible=true, opacity=1, normal blending, and zero
|
|
127
|
+
offsets/rotation. V2 stacks preserve their layers. Both return
|
|
128
|
+
`DOCUMENT_VERSION_MIGRATED` and canonical v3 output.
|
|
129
|
+
|
|
130
|
+
Legacy `textmode.layer@1.0.0` and `textmode.layer@1.1.0` migrate to one or more
|
|
131
|
+
layers and return `LEGACY_DOCUMENT_MIGRATED`. `acceptLegacy: false` rejects these
|
|
132
|
+
textmode.layer files but still accepts supported textmode.document v2 files.
|
|
133
|
+
The [v2 specification](specification-2.0.md) and schema remain available; their
|
|
134
|
+
wire-format constraints are unchanged.
|
package/package.json
CHANGED
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@textmode/document",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Portable textmode document format, JSON Schema, types, and dependency-free decoder.",
|
|
5
|
+
"license": "CC0-1.0",
|
|
6
|
+
"author": "humanbydefinition <hello@textmode.art> (https://code.textmode.art)",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"devEngines": {
|
|
10
|
+
"runtime": {
|
|
11
|
+
"name": "node",
|
|
12
|
+
"version": ">=24.0.0",
|
|
13
|
+
"onFail": "error"
|
|
14
|
+
},
|
|
15
|
+
"packageManager": {
|
|
16
|
+
"name": "npm",
|
|
17
|
+
"version": "^11.0.0",
|
|
18
|
+
"onFail": "error"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=24"
|
|
23
|
+
},
|
|
24
|
+
"packageManager": "npm@11.16.0",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./schema/2.0": {
|
|
33
|
+
"types": "./schemas/2.0/schema.d.ts",
|
|
34
|
+
"default": "./schemas/2.0/schema.json"
|
|
35
|
+
},
|
|
36
|
+
"./schema/3.0": {
|
|
37
|
+
"types": "./schemas/3.0/schema.d.ts",
|
|
38
|
+
"default": "./schemas/3.0/schema.json"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist/**/*.js",
|
|
43
|
+
"dist/**/*.d.ts",
|
|
44
|
+
"schemas/2.0/schema.json",
|
|
45
|
+
"schemas/2.0/schema.d.ts",
|
|
46
|
+
"docs/*.md",
|
|
47
|
+
"schemas/3.0/schema.json",
|
|
48
|
+
"schemas/3.0/schema.d.ts"
|
|
49
|
+
],
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/humanbydefinition/textmode-document.git"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/humanbydefinition/textmode-document/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/humanbydefinition/textmode-document#readme",
|
|
61
|
+
"scripts": {
|
|
62
|
+
"clean": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\"",
|
|
63
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
64
|
+
"format": "textmode-lint format",
|
|
65
|
+
"format:check": "textmode-lint format:check",
|
|
66
|
+
"lint": "textmode-lint lint src tests && node --check scripts/check-package.mjs",
|
|
67
|
+
"lint:markdown": "textmode-lint lint:markdown",
|
|
68
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
69
|
+
"docs:check": "textmode-docs check",
|
|
70
|
+
"docs:build": "textmode-docs build",
|
|
71
|
+
"test": "vitest run --dir tests",
|
|
72
|
+
"check:package": "node scripts/check-package.mjs",
|
|
73
|
+
"check": "npm run format:check && npm run lint && npm run lint:markdown && npm run typecheck && npm run docs:check && npm test && npm run build && npm run check:package",
|
|
74
|
+
"prepare": "npm run build"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@textmode/docs": "^0.3.0",
|
|
78
|
+
"@textmode/lint": "^0.1.2",
|
|
79
|
+
"@textmode/tsconfig": "^0.2.2",
|
|
80
|
+
"@types/node": "^26.4.1",
|
|
81
|
+
"ajv": "^8.20.0",
|
|
82
|
+
"ajv-formats": "^3.0.1",
|
|
83
|
+
"typescript": "^6.0.3",
|
|
84
|
+
"vitest": "^5.0.0"
|
|
85
|
+
},
|
|
86
|
+
"prettier": "@textmode/lint/prettier"
|
|
87
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://textmode.art/schemas/textmode-document-2.0.schema.json",
|
|
4
|
+
"title": "Textmode document 2.0",
|
|
5
|
+
"description": "Editable textmode cell snapshots and layer stacks.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"$ref": "#/$defs/selectedDocument"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"$ref": "#/$defs/allDocument"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"$defs": {
|
|
15
|
+
"positiveSafeInteger": {
|
|
16
|
+
"type": "integer",
|
|
17
|
+
"minimum": 1,
|
|
18
|
+
"maximum": 9007199254740991,
|
|
19
|
+
"description": "Positive integer in the interoperable JavaScript safe range."
|
|
20
|
+
},
|
|
21
|
+
"canvas": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["width", "height"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"width": {
|
|
26
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
27
|
+
"description": "Width in pixels; the decoder checks equality with the corresponding grid product."
|
|
28
|
+
},
|
|
29
|
+
"height": {
|
|
30
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
31
|
+
"description": "Height in pixels; the decoder checks equality with the corresponding grid product."
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": true,
|
|
35
|
+
"description": "Legacy grid pixel extent. For selected documents this equals the grid extent; for all documents it equals the first layer grid extent. It is not a composite viewport. See docs/specification-2.0.md#canvas-field."
|
|
36
|
+
},
|
|
37
|
+
"grid": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["cols", "rows", "cellWidth", "cellHeight"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"cols": {
|
|
42
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
43
|
+
"description": "Positive number of columns."
|
|
44
|
+
},
|
|
45
|
+
"rows": {
|
|
46
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
47
|
+
"description": "Positive number of rows."
|
|
48
|
+
},
|
|
49
|
+
"cellWidth": {
|
|
50
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
51
|
+
"description": "Positive cell width in pixels."
|
|
52
|
+
},
|
|
53
|
+
"cellHeight": {
|
|
54
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
55
|
+
"description": "Positive cell height in pixels."
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": true,
|
|
59
|
+
"description": "Logical cell count and pixel dimensions of each cell. Products must remain safe integers."
|
|
60
|
+
},
|
|
61
|
+
"rgba": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["r", "g", "b", "a"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"r": {
|
|
66
|
+
"type": "integer",
|
|
67
|
+
"minimum": 0,
|
|
68
|
+
"maximum": 255,
|
|
69
|
+
"description": "Red channel."
|
|
70
|
+
},
|
|
71
|
+
"g": {
|
|
72
|
+
"type": "integer",
|
|
73
|
+
"minimum": 0,
|
|
74
|
+
"maximum": 255,
|
|
75
|
+
"description": "Green channel."
|
|
76
|
+
},
|
|
77
|
+
"b": {
|
|
78
|
+
"type": "integer",
|
|
79
|
+
"minimum": 0,
|
|
80
|
+
"maximum": 255,
|
|
81
|
+
"description": "Blue channel."
|
|
82
|
+
},
|
|
83
|
+
"a": {
|
|
84
|
+
"type": "integer",
|
|
85
|
+
"minimum": 0,
|
|
86
|
+
"maximum": 255,
|
|
87
|
+
"description": "Alpha: 0 is transparent and 255 is opaque."
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"additionalProperties": true,
|
|
91
|
+
"description": "Unpremultiplied RGBA integer channels from 0 through 255."
|
|
92
|
+
},
|
|
93
|
+
"color": {
|
|
94
|
+
"oneOf": [
|
|
95
|
+
{
|
|
96
|
+
"type": "string",
|
|
97
|
+
"pattern": "^#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?$"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"$ref": "#/$defs/rgba"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"description": "RGB or RGBA hexadecimal notation, or explicit RGBA channels. Six-digit notation implies alpha 255."
|
|
104
|
+
},
|
|
105
|
+
"transform": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": ["invert", "flipX", "flipY", "rotation"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"invert": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"description": "Glyph inversion; alpha means this cannot generally be replaced by swapping colors."
|
|
112
|
+
},
|
|
113
|
+
"flipX": {
|
|
114
|
+
"type": "boolean",
|
|
115
|
+
"description": "Mirror the glyph horizontally."
|
|
116
|
+
},
|
|
117
|
+
"flipY": {
|
|
118
|
+
"type": "boolean",
|
|
119
|
+
"description": "Mirror the glyph vertically."
|
|
120
|
+
},
|
|
121
|
+
"rotation": {
|
|
122
|
+
"type": "number",
|
|
123
|
+
"description": "Cell rotation in degrees; normalized by the decoder to [0, 360)."
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": true,
|
|
127
|
+
"description": "Per-cell glyph transforms. Rotation is finite degrees; decoding normalizes it to [0, 360)."
|
|
128
|
+
},
|
|
129
|
+
"cell": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": ["x", "y", "character", "foreground", "background", "transform"],
|
|
132
|
+
"properties": {
|
|
133
|
+
"x": {
|
|
134
|
+
"type": "integer",
|
|
135
|
+
"minimum": 0,
|
|
136
|
+
"maximum": 9007199254740991,
|
|
137
|
+
"description": "Zero-based column index; must equal the position within its row."
|
|
138
|
+
},
|
|
139
|
+
"y": {
|
|
140
|
+
"type": "integer",
|
|
141
|
+
"minimum": 0,
|
|
142
|
+
"maximum": 9007199254740991,
|
|
143
|
+
"description": "Zero-based row index; must equal the row position."
|
|
144
|
+
},
|
|
145
|
+
"character": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"minLength": 1,
|
|
148
|
+
"maxLength": 64,
|
|
149
|
+
"description": "Nonempty Unicode glyph string, preserved without normalization. The decoder rejects unpaired surrogates and enforces a scalar-count budget."
|
|
150
|
+
},
|
|
151
|
+
"foreground": {
|
|
152
|
+
"$ref": "#/$defs/color",
|
|
153
|
+
"description": "Glyph color with alpha."
|
|
154
|
+
},
|
|
155
|
+
"background": {
|
|
156
|
+
"$ref": "#/$defs/color",
|
|
157
|
+
"description": "Cell background color with alpha. A space with a visible background remains meaningful."
|
|
158
|
+
},
|
|
159
|
+
"transform": {
|
|
160
|
+
"$ref": "#/$defs/transform",
|
|
161
|
+
"description": "Per-cell glyph inversion, flips, and rotation."
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"additionalProperties": true,
|
|
165
|
+
"description": "One cell at its redundant zero-based row and column. Coordinates must match array position; this cross-field rule is checked by the decoder."
|
|
166
|
+
},
|
|
167
|
+
"cells": {
|
|
168
|
+
"type": "object",
|
|
169
|
+
"required": ["encoding", "rows"],
|
|
170
|
+
"properties": {
|
|
171
|
+
"encoding": {
|
|
172
|
+
"const": "object-rows-v1",
|
|
173
|
+
"description": "Tagged cell encoding; v2 supports only object-rows-v1."
|
|
174
|
+
},
|
|
175
|
+
"rows": {
|
|
176
|
+
"type": "array",
|
|
177
|
+
"items": {
|
|
178
|
+
"type": "array",
|
|
179
|
+
"items": {
|
|
180
|
+
"$ref": "#/$defs/cell"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"description": "Dense row-major arrays; row count and lengths must equal the owning grid. The decoder checks this invariant."
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"additionalProperties": true,
|
|
187
|
+
"description": "Dense row-major cells, including transparent cells. Row count and lengths must equal the grid. See docs/specification-2.0.md#cell-encoding."
|
|
188
|
+
},
|
|
189
|
+
"metadata": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"required": ["createdAt", "generator"],
|
|
192
|
+
"properties": {
|
|
193
|
+
"createdAt": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"format": "date-time",
|
|
196
|
+
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$",
|
|
197
|
+
"description": "Real calendar date and time with uppercase T and timezone, required seconds, optional fractional seconds, and no leap seconds. Enable date-time format assertions in validators."
|
|
198
|
+
},
|
|
199
|
+
"generator": {
|
|
200
|
+
"type": "object",
|
|
201
|
+
"required": ["name", "version"],
|
|
202
|
+
"properties": {
|
|
203
|
+
"name": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"minLength": 1
|
|
206
|
+
},
|
|
207
|
+
"version": {
|
|
208
|
+
"type": "string",
|
|
209
|
+
"minLength": 1
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"additionalProperties": true,
|
|
213
|
+
"description": "Producer identity; it does not select executable behavior."
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"additionalProperties": true,
|
|
217
|
+
"description": "Optional informational creation and generator metadata. When present, both members are required."
|
|
218
|
+
},
|
|
219
|
+
"selectedLayer": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"required": ["id", "cells"],
|
|
222
|
+
"properties": {
|
|
223
|
+
"id": {
|
|
224
|
+
"type": "string",
|
|
225
|
+
"minLength": 1,
|
|
226
|
+
"description": "Nonempty informational source identifier. It is not a persistent cross-document identity; the decoder does not enforce uniqueness."
|
|
227
|
+
},
|
|
228
|
+
"cells": {
|
|
229
|
+
"$ref": "#/$defs/cells",
|
|
230
|
+
"description": "Complete dense cell rows."
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"additionalProperties": true,
|
|
234
|
+
"description": "Informational source ID and cells. A selected snapshot does not carry layer presentation or font identity."
|
|
235
|
+
},
|
|
236
|
+
"stackLayer": {
|
|
237
|
+
"type": "object",
|
|
238
|
+
"required": ["id", "visible", "opacity", "blendMode", "offsetX", "offsetY", "rotationZ", "grid", "cells"],
|
|
239
|
+
"properties": {
|
|
240
|
+
"id": {
|
|
241
|
+
"type": "string",
|
|
242
|
+
"minLength": 1,
|
|
243
|
+
"description": "Nonempty informational source identifier. It is not a persistent cross-document identity; the decoder does not enforce uniqueness."
|
|
244
|
+
},
|
|
245
|
+
"visible": {
|
|
246
|
+
"type": "boolean",
|
|
247
|
+
"description": "Whether the layer participates in presentation. Hidden cells are still stored."
|
|
248
|
+
},
|
|
249
|
+
"opacity": {
|
|
250
|
+
"type": "number",
|
|
251
|
+
"minimum": 0,
|
|
252
|
+
"maximum": 1,
|
|
253
|
+
"description": "Layer opacity from 0 through 1, separate from cell alpha."
|
|
254
|
+
},
|
|
255
|
+
"blendMode": {
|
|
256
|
+
"type": "string",
|
|
257
|
+
"minLength": 1,
|
|
258
|
+
"description": "Nonempty blend mode name. The decoder preserves unknown names; apps decide which they can present."
|
|
259
|
+
},
|
|
260
|
+
"offsetX": {
|
|
261
|
+
"type": "number",
|
|
262
|
+
"description": "Horizontal compositing offset in pixels."
|
|
263
|
+
},
|
|
264
|
+
"offsetY": {
|
|
265
|
+
"type": "number",
|
|
266
|
+
"description": "Vertical compositing offset in pixels."
|
|
267
|
+
},
|
|
268
|
+
"rotationZ": {
|
|
269
|
+
"type": "number",
|
|
270
|
+
"description": "Layer rotation in degrees, clockwise about the layer center in textmode.js; preserved by the decoder."
|
|
271
|
+
},
|
|
272
|
+
"grid": {
|
|
273
|
+
"$ref": "#/$defs/grid",
|
|
274
|
+
"description": "Grid dimensions and cell metrics."
|
|
275
|
+
},
|
|
276
|
+
"cells": {
|
|
277
|
+
"$ref": "#/$defs/cells",
|
|
278
|
+
"description": "Complete dense cell rows."
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"additionalProperties": true,
|
|
282
|
+
"description": "One ordered layer with independent grid, cells, and presentation values."
|
|
283
|
+
},
|
|
284
|
+
"selectedDocument": {
|
|
285
|
+
"type": "object",
|
|
286
|
+
"required": ["format", "formatVersion", "target", "canvas", "grid", "layer"],
|
|
287
|
+
"properties": {
|
|
288
|
+
"format": {
|
|
289
|
+
"const": "textmode.document",
|
|
290
|
+
"description": "Required format discriminator."
|
|
291
|
+
},
|
|
292
|
+
"formatVersion": {
|
|
293
|
+
"const": "2.0.0",
|
|
294
|
+
"description": "On-disk format version, independent of package versions."
|
|
295
|
+
},
|
|
296
|
+
"target": {
|
|
297
|
+
"const": "selected",
|
|
298
|
+
"description": "Shape discriminator: selected uses layer and root grid; all uses layers."
|
|
299
|
+
},
|
|
300
|
+
"metadata": {
|
|
301
|
+
"$ref": "#/$defs/metadata",
|
|
302
|
+
"description": "metadata"
|
|
303
|
+
},
|
|
304
|
+
"canvas": {
|
|
305
|
+
"$ref": "#/$defs/canvas",
|
|
306
|
+
"description": "Legacy grid pixel extent, not the final canvas or a composite viewport."
|
|
307
|
+
},
|
|
308
|
+
"grid": {
|
|
309
|
+
"$ref": "#/$defs/grid",
|
|
310
|
+
"description": "Grid dimensions and cell metrics."
|
|
311
|
+
},
|
|
312
|
+
"layer": {
|
|
313
|
+
"$ref": "#/$defs/selectedLayer",
|
|
314
|
+
"description": "One cell snapshot."
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"additionalProperties": true,
|
|
318
|
+
"description": "Selected cell snapshot. All listed properties except metadata are required."
|
|
319
|
+
},
|
|
320
|
+
"allDocument": {
|
|
321
|
+
"type": "object",
|
|
322
|
+
"required": ["format", "formatVersion", "target", "canvas", "layers"],
|
|
323
|
+
"properties": {
|
|
324
|
+
"format": {
|
|
325
|
+
"const": "textmode.document",
|
|
326
|
+
"description": "Required format discriminator."
|
|
327
|
+
},
|
|
328
|
+
"formatVersion": {
|
|
329
|
+
"const": "2.0.0",
|
|
330
|
+
"description": "On-disk format version, independent of package versions."
|
|
331
|
+
},
|
|
332
|
+
"target": {
|
|
333
|
+
"const": "all",
|
|
334
|
+
"description": "Shape discriminator: selected uses layer and root grid; all uses layers."
|
|
335
|
+
},
|
|
336
|
+
"metadata": {
|
|
337
|
+
"$ref": "#/$defs/metadata",
|
|
338
|
+
"description": "metadata"
|
|
339
|
+
},
|
|
340
|
+
"canvas": {
|
|
341
|
+
"$ref": "#/$defs/canvas",
|
|
342
|
+
"description": "Legacy grid pixel extent, not the final canvas or a composite viewport."
|
|
343
|
+
},
|
|
344
|
+
"layers": {
|
|
345
|
+
"type": "array",
|
|
346
|
+
"minItems": 1,
|
|
347
|
+
"items": {
|
|
348
|
+
"$ref": "#/$defs/stackLayer"
|
|
349
|
+
},
|
|
350
|
+
"description": "Nonempty ordered layer stack, bottom first."
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
"additionalProperties": true,
|
|
354
|
+
"description": "One or more layers in bottom-to-top order. The decoder applies cumulative cell and layer budgets."
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|