@textmode/document 1.0.0 → 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/README.md +9 -8
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/decode-shapes.d.ts +5 -3
- package/dist/decode-shapes.js +41 -13
- package/dist/decode.js +8 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/types/canonical.d.ts +3 -11
- package/dist/types/decode.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/legacy.d.ts +4 -3
- package/dist/types/v2.d.ts +34 -0
- package/dist/types/v2.js +1 -0
- package/dist/types/wire.d.ts +2 -34
- package/docs/consumer-guide.md +99 -66
- package/docs/schema-2.0.md +236 -0
- package/docs/schema-3.0.md +117 -0
- package/docs/specification-2.0.md +30 -34
- package/docs/specification-3.0.md +134 -0
- package/package.json +9 -4
- package/schemas/3.0/schema.d.ts +3 -0
- package/schemas/3.0/schema.json +267 -0
- package/docs/maintenance.md +0 -56
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Textmode document format 2.0
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
specification, schema, and decoder are maintained by `textmode-document` and
|
|
7
|
-
published as `@textmode/document`. The raw schema is available from
|
|
8
|
-
`@textmode/document/schema/2.0` and [the repository](../schemas/2.0/schema.json).
|
|
9
|
-
The schema's `$id` is a stable identifier, not a hosted download URL.
|
|
7
|
+
## Status
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
is
|
|
13
|
-
|
|
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.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
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
17
|
|
|
18
18
|
## Encoding
|
|
19
19
|
|
|
@@ -25,9 +25,8 @@ profile in RFC 7493:
|
|
|
25
25
|
- non-finite numbers are not emitted;
|
|
26
26
|
- timestamps use an uppercase, timezone-qualified RFC 3339 representation.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
last value. No duplicate-key detection is claimed by this codec.
|
|
28
|
+
The JavaScript parser uses `JSON.parse`, which keeps the last value for a
|
|
29
|
+
repeated key. It does not report duplicate keys.
|
|
31
30
|
|
|
32
31
|
The media type is `application/json`. `.textmode.json` is a suggested filename
|
|
33
32
|
suffix; existing exporter downloads use `.json` by default. Extensions and
|
|
@@ -57,16 +56,15 @@ container format.
|
|
|
57
56
|
|
|
58
57
|
## Version compatibility
|
|
59
58
|
|
|
60
|
-
The version
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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.
|
|
64
63
|
|
|
65
|
-
For a supported version, consumers ignore unknown ordinary
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
`encoding` because those values change interpretation.
|
|
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.
|
|
70
68
|
|
|
71
69
|
Version 2 does not define `extensionsUsed`, `extensionsRequired`, extension
|
|
72
70
|
namespacing, or an extension registry. A later revision must define those
|
|
@@ -128,8 +126,7 @@ canvas.height = grid.rows × grid.cellHeight
|
|
|
128
126
|
For an `all` document, the root extent is derived from the first layer's grid.
|
|
129
127
|
Consumers must not infer composite placement or clipping from this field.
|
|
130
128
|
|
|
131
|
-
|
|
132
|
-
semantics must not be changed in place.
|
|
129
|
+
The meaning of this field must not change within version 2.
|
|
133
130
|
|
|
134
131
|
## Cell encoding
|
|
135
132
|
|
|
@@ -231,10 +228,9 @@ throws `TypeError`. Invalid document data instead returns a typed failure.
|
|
|
231
228
|
| `maxCharacterLength` | 64 | Unicode scalar values per glyph string. |
|
|
232
229
|
| `maxMetadataStringLength` | 4,096 | UTF-16 code units per metadata string and blend-mode name. |
|
|
233
230
|
|
|
234
|
-
These budgets apply after JSON parsing and do not
|
|
235
|
-
memory. The schema's
|
|
236
|
-
|
|
237
|
-
`create.textmode.art` may accept fewer cells than another document consumer.
|
|
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.
|
|
238
234
|
|
|
239
235
|
Consumers should:
|
|
240
236
|
|
|
@@ -254,14 +250,14 @@ The compatibility decoder recognizes:
|
|
|
254
250
|
- `textmode.layer@1.0.0` as a selected cell snapshot;
|
|
255
251
|
- `textmode.layer@1.1.0` as a layer stack.
|
|
256
252
|
|
|
257
|
-
|
|
258
|
-
|
|
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.
|
|
259
256
|
|
|
260
257
|
## JSON Schema and runtime decoder
|
|
261
258
|
|
|
262
|
-
The specification defines meaning and cross-field rules. The JSON Schema describes
|
|
263
|
-
|
|
264
|
-
for:
|
|
259
|
+
The specification defines meaning and cross-field rules. The JSON Schema describes
|
|
260
|
+
structure for general tooling. The decoder also checks or applies:
|
|
265
261
|
|
|
266
262
|
- cross-field row and grid invariants;
|
|
267
263
|
- grid pixel extent equality;
|
|
@@ -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,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@textmode/document",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Portable textmode document format, JSON Schema, types, and dependency-free decoder.",
|
|
5
5
|
"license": "CC0-1.0",
|
|
6
6
|
"author": "humanbydefinition <hello@textmode.art> (https://code.textmode.art)",
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"./schema/2.0": {
|
|
33
33
|
"types": "./schemas/2.0/schema.d.ts",
|
|
34
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"
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
"files": [
|
|
@@ -39,7 +43,9 @@
|
|
|
39
43
|
"dist/**/*.d.ts",
|
|
40
44
|
"schemas/2.0/schema.json",
|
|
41
45
|
"schemas/2.0/schema.d.ts",
|
|
42
|
-
"docs/*.md"
|
|
46
|
+
"docs/*.md",
|
|
47
|
+
"schemas/3.0/schema.json",
|
|
48
|
+
"schemas/3.0/schema.d.ts"
|
|
43
49
|
],
|
|
44
50
|
"publishConfig": {
|
|
45
51
|
"access": "public"
|
|
@@ -62,10 +68,9 @@
|
|
|
62
68
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
63
69
|
"docs:check": "textmode-docs check",
|
|
64
70
|
"docs:build": "textmode-docs build",
|
|
65
|
-
"test": "vitest run",
|
|
71
|
+
"test": "vitest run --dir tests",
|
|
66
72
|
"check:package": "node scripts/check-package.mjs",
|
|
67
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",
|
|
68
|
-
"prepack": "npm run build",
|
|
69
74
|
"prepare": "npm run build"
|
|
70
75
|
},
|
|
71
76
|
"devDependencies": {
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://textmode.art/schemas/textmode-document-3.0.schema.json",
|
|
4
|
+
"title": "Textmode document 3.0",
|
|
5
|
+
"description": "One or more portable textmode layers in bottom-to-top order. See docs/specification-3.0.md.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"positiveSafeInteger": {
|
|
8
|
+
"type": "integer",
|
|
9
|
+
"minimum": 1,
|
|
10
|
+
"maximum": 9007199254740991,
|
|
11
|
+
"description": "Positive integer in the interoperable JavaScript safe range."
|
|
12
|
+
},
|
|
13
|
+
"grid": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["cols", "rows", "cellWidth", "cellHeight"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"cols": {
|
|
18
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
19
|
+
"description": "Positive number of columns."
|
|
20
|
+
},
|
|
21
|
+
"rows": {
|
|
22
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
23
|
+
"description": "Positive number of rows."
|
|
24
|
+
},
|
|
25
|
+
"cellWidth": {
|
|
26
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
27
|
+
"description": "Positive cell width in pixels."
|
|
28
|
+
},
|
|
29
|
+
"cellHeight": {
|
|
30
|
+
"$ref": "#/$defs/positiveSafeInteger",
|
|
31
|
+
"description": "Positive cell height in pixels."
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": true,
|
|
35
|
+
"description": "Logical cell count and pixel dimensions of each cell. Products must remain safe integers."
|
|
36
|
+
},
|
|
37
|
+
"rgba": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["r", "g", "b", "a"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"r": {
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"minimum": 0,
|
|
44
|
+
"maximum": 255,
|
|
45
|
+
"description": "Red channel."
|
|
46
|
+
},
|
|
47
|
+
"g": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 0,
|
|
50
|
+
"maximum": 255,
|
|
51
|
+
"description": "Green channel."
|
|
52
|
+
},
|
|
53
|
+
"b": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"minimum": 0,
|
|
56
|
+
"maximum": 255,
|
|
57
|
+
"description": "Blue channel."
|
|
58
|
+
},
|
|
59
|
+
"a": {
|
|
60
|
+
"type": "integer",
|
|
61
|
+
"minimum": 0,
|
|
62
|
+
"maximum": 255,
|
|
63
|
+
"description": "Alpha: 0 is transparent and 255 is opaque."
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"additionalProperties": true,
|
|
67
|
+
"description": "Unpremultiplied RGBA integer channels from 0 through 255."
|
|
68
|
+
},
|
|
69
|
+
"color": {
|
|
70
|
+
"oneOf": [
|
|
71
|
+
{
|
|
72
|
+
"type": "string",
|
|
73
|
+
"pattern": "^#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?$"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"$ref": "#/$defs/rgba"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"description": "RGB or RGBA hexadecimal notation, or explicit RGBA channels. Six-digit notation implies alpha 255."
|
|
80
|
+
},
|
|
81
|
+
"transform": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"required": ["invert", "flipX", "flipY", "rotation"],
|
|
84
|
+
"properties": {
|
|
85
|
+
"invert": {
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"description": "Glyph inversion; alpha means this cannot generally be replaced by swapping colors."
|
|
88
|
+
},
|
|
89
|
+
"flipX": {
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"description": "Mirror the glyph horizontally."
|
|
92
|
+
},
|
|
93
|
+
"flipY": {
|
|
94
|
+
"type": "boolean",
|
|
95
|
+
"description": "Mirror the glyph vertically."
|
|
96
|
+
},
|
|
97
|
+
"rotation": {
|
|
98
|
+
"type": "number",
|
|
99
|
+
"description": "Cell rotation in degrees; normalized by the decoder to [0, 360)."
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"additionalProperties": true,
|
|
103
|
+
"description": "Per-cell glyph transforms. Rotation is finite degrees; decoding normalizes it to [0, 360)."
|
|
104
|
+
},
|
|
105
|
+
"cell": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": ["x", "y", "character", "foreground", "background", "transform"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"x": {
|
|
110
|
+
"type": "integer",
|
|
111
|
+
"minimum": 0,
|
|
112
|
+
"maximum": 9007199254740991,
|
|
113
|
+
"description": "Zero-based column index; must equal the position within its row."
|
|
114
|
+
},
|
|
115
|
+
"y": {
|
|
116
|
+
"type": "integer",
|
|
117
|
+
"minimum": 0,
|
|
118
|
+
"maximum": 9007199254740991,
|
|
119
|
+
"description": "Zero-based row index; must equal the row position."
|
|
120
|
+
},
|
|
121
|
+
"character": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"minLength": 1,
|
|
124
|
+
"maxLength": 64,
|
|
125
|
+
"description": "Nonempty Unicode glyph string, preserved without normalization. The decoder rejects unpaired surrogates and enforces a scalar-count budget."
|
|
126
|
+
},
|
|
127
|
+
"foreground": {
|
|
128
|
+
"$ref": "#/$defs/color",
|
|
129
|
+
"description": "Glyph color with alpha."
|
|
130
|
+
},
|
|
131
|
+
"background": {
|
|
132
|
+
"$ref": "#/$defs/color",
|
|
133
|
+
"description": "Cell background color with alpha. A space with a visible background remains meaningful."
|
|
134
|
+
},
|
|
135
|
+
"transform": {
|
|
136
|
+
"$ref": "#/$defs/transform",
|
|
137
|
+
"description": "Per-cell glyph inversion, flips, and rotation."
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"additionalProperties": true,
|
|
141
|
+
"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."
|
|
142
|
+
},
|
|
143
|
+
"cells": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"required": ["encoding", "rows"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"encoding": {
|
|
148
|
+
"const": "object-rows-v1",
|
|
149
|
+
"description": "Tagged cell encoding; v2 supports only object-rows-v1."
|
|
150
|
+
},
|
|
151
|
+
"rows": {
|
|
152
|
+
"type": "array",
|
|
153
|
+
"items": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"items": {
|
|
156
|
+
"$ref": "#/$defs/cell"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"description": "Dense row-major arrays; row count and lengths must equal the owning grid. The decoder checks this invariant."
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"additionalProperties": true,
|
|
163
|
+
"description": "Dense row-major cells, including transparent cells. Row count and lengths must equal the grid. See docs/specification-3.0.md#cells-and-coordinates."
|
|
164
|
+
},
|
|
165
|
+
"metadata": {
|
|
166
|
+
"type": "object",
|
|
167
|
+
"required": ["createdAt", "generator"],
|
|
168
|
+
"properties": {
|
|
169
|
+
"createdAt": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"format": "date-time",
|
|
172
|
+
"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])$",
|
|
173
|
+
"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."
|
|
174
|
+
},
|
|
175
|
+
"generator": {
|
|
176
|
+
"type": "object",
|
|
177
|
+
"required": ["name", "version"],
|
|
178
|
+
"properties": {
|
|
179
|
+
"name": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"minLength": 1
|
|
182
|
+
},
|
|
183
|
+
"version": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"minLength": 1
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"additionalProperties": true,
|
|
189
|
+
"description": "Producer identity; it does not select executable behavior."
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"additionalProperties": true,
|
|
193
|
+
"description": "Optional informational creation and generator metadata. When present, both members are required."
|
|
194
|
+
},
|
|
195
|
+
"layer": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"required": ["id", "visible", "opacity", "blendMode", "offsetX", "offsetY", "rotationZ", "grid", "cells"],
|
|
198
|
+
"properties": {
|
|
199
|
+
"id": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"minLength": 1,
|
|
202
|
+
"description": "Nonempty informational source identifier. It is not a persistent cross-document identity; the decoder does not enforce uniqueness."
|
|
203
|
+
},
|
|
204
|
+
"visible": {
|
|
205
|
+
"type": "boolean",
|
|
206
|
+
"description": "Whether the layer participates in presentation. Hidden cells are still stored."
|
|
207
|
+
},
|
|
208
|
+
"opacity": {
|
|
209
|
+
"type": "number",
|
|
210
|
+
"minimum": 0,
|
|
211
|
+
"maximum": 1,
|
|
212
|
+
"description": "Layer opacity from 0 through 1, separate from cell alpha."
|
|
213
|
+
},
|
|
214
|
+
"blendMode": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"minLength": 1,
|
|
217
|
+
"description": "Nonempty blend mode name. The decoder preserves unknown names; apps decide which they can present."
|
|
218
|
+
},
|
|
219
|
+
"offsetX": { "type": "number", "description": "Horizontal compositing offset in pixels." },
|
|
220
|
+
"offsetY": { "type": "number", "description": "Vertical compositing offset in pixels." },
|
|
221
|
+
"rotationZ": {
|
|
222
|
+
"type": "number",
|
|
223
|
+
"description": "Layer rotation in degrees, clockwise about the layer center in textmode.js; preserved by the decoder."
|
|
224
|
+
},
|
|
225
|
+
"grid": {
|
|
226
|
+
"$ref": "#/$defs/grid",
|
|
227
|
+
"description": "Grid dimensions and cell metrics."
|
|
228
|
+
},
|
|
229
|
+
"cells": {
|
|
230
|
+
"$ref": "#/$defs/cells",
|
|
231
|
+
"description": "Complete dense cell rows."
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
"additionalProperties": true,
|
|
235
|
+
"description": "One complete layer, including for documents containing a single layer."
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"type": "object",
|
|
239
|
+
"required": ["format", "formatVersion", "layers"],
|
|
240
|
+
"properties": {
|
|
241
|
+
"format": {
|
|
242
|
+
"const": "textmode.document",
|
|
243
|
+
"description": "Required portable document format discriminator."
|
|
244
|
+
},
|
|
245
|
+
"formatVersion": {
|
|
246
|
+
"const": "3.0.0",
|
|
247
|
+
"description": "On-disk format version, independent of package versions."
|
|
248
|
+
},
|
|
249
|
+
"metadata": {
|
|
250
|
+
"$ref": "#/$defs/metadata",
|
|
251
|
+
"description": "Optional informational timestamp and producer identity."
|
|
252
|
+
},
|
|
253
|
+
"layers": {
|
|
254
|
+
"type": "array",
|
|
255
|
+
"minItems": 1,
|
|
256
|
+
"items": {
|
|
257
|
+
"$ref": "#/$defs/layer"
|
|
258
|
+
},
|
|
259
|
+
"description": "Nonempty ordered layer stack, bottom first."
|
|
260
|
+
},
|
|
261
|
+
"target": false,
|
|
262
|
+
"layer": false,
|
|
263
|
+
"grid": false,
|
|
264
|
+
"canvas": false
|
|
265
|
+
},
|
|
266
|
+
"additionalProperties": true
|
|
267
|
+
}
|
package/docs/maintenance.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Maintaining the document contract
|
|
2
|
-
|
|
3
|
-
## Ownership
|
|
4
|
-
|
|
5
|
-
This repository owns the format specification, versioned JSON Schema, portable
|
|
6
|
-
types, decoder, and protocol checks. `textmode.export.js` owns renderer extraction,
|
|
7
|
-
selection, generator metadata, serialization options, and downloads. Apps own
|
|
8
|
-
placement, history, font selection, and supported-feature decisions.
|
|
9
|
-
|
|
10
|
-
The schema describes portable structure and local constraints. The specification
|
|
11
|
-
defines meaning and cross-field rules. The decoder implements these rules plus
|
|
12
|
-
normalization and caller budgets. Keep all three consistent through contract
|
|
13
|
-
tests; do not generate competing schemas or copy protocol logic into callers.
|
|
14
|
-
|
|
15
|
-
## Compatibility
|
|
16
|
-
|
|
17
|
-
Package releases and on-disk versions are independent. Preserve the meaning of
|
|
18
|
-
`textmode.document@2.0.0`, including its selected/all distinction and legacy
|
|
19
|
-
`canvas` field. A new document version needs an explicit reader, specification,
|
|
20
|
-
schema, and compatibility tests before it is accepted. Do not infer support for
|
|
21
|
-
future `2.x` files. Namespaced or required extensions need a future design.
|
|
22
|
-
|
|
23
|
-
Keep published versioned schemas stable. Documentation annotations may improve;
|
|
24
|
-
constraint or semantic changes require a compatibility review and explicit
|
|
25
|
-
versioning. Changes to normalized results, error codes, paths, migration warnings,
|
|
26
|
-
or default limits are also public behavior. Deprecate a JavaScript interface before
|
|
27
|
-
removing it from a stable package major.
|
|
28
|
-
|
|
29
|
-
## Tests and documentation
|
|
30
|
-
|
|
31
|
-
Fixtures are fixed test inputs. They protect legacy migration and edge cases and
|
|
32
|
-
are neither demos nor supported package imports. Keep them in `tests/fixtures`,
|
|
33
|
-
out of the package allowlist. Read the fixture README before changing their data.
|
|
34
|
-
|
|
35
|
-
Validate every complete `json` block in the consumer guide against the schema
|
|
36
|
-
and decoder. Keep syntax fragments in the specification clearly labeled. Run
|
|
37
|
-
`npm run check` for formatting, lint, Markdown, types, API docs, tests, build, and
|
|
38
|
-
an isolated packed-package check. The docs build disables browser sketch checks
|
|
39
|
-
because this package has no browser gallery.
|
|
40
|
-
|
|
41
|
-
## npm releases
|
|
42
|
-
|
|
43
|
-
The package is prepared for npm as `@textmode/document`. Its package version is
|
|
44
|
-
independent of the on-disk `textmode.document@2.0.0` format version. A package
|
|
45
|
-
release does not change the document format.
|
|
46
|
-
|
|
47
|
-
Before publishing, run `npm run check` and inspect `npm pack --dry-run`. Confirm
|
|
48
|
-
that the archive contains the runtime, declarations, schemas, and consumer docs,
|
|
49
|
-
and excludes fixtures and tests. This repository has no automated npm publish
|
|
50
|
-
workflow.
|
|
51
|
-
|
|
52
|
-
The exporter currently pins this repository at an exact Git commit. When the
|
|
53
|
-
registry release is ready for consumer adoption, update the exporter to the exact
|
|
54
|
-
published version and regenerate its lockfile with npm. The source repository is
|
|
55
|
-
private, so Git-based installs require authenticated read access until consumers
|
|
56
|
-
move to the public npm package.
|