Supernote .note (Ratta)¶
Reader: core/src/inkterop/formats/supernote/ (SupernoteReader,
raster-first). Backed by supernotelib
(Apache-2.0), pinned >=0.7.1 in core/pyproject.toml; behavior below
was verified against supernotelib 0.7.1 source and synthetic
fixtures (core/tests/fixtures/supernote/). No real device files were
available during development — everything not exercised by the fixtures
is marked accordingly.
Confidence markers: [verified] = exercised by tests against supernotelib 0.7.1; [inferred] = read from supernotelib source but not exercised; [unknown] = no evidence either way.
Container overview¶
- [verified] X-series files: 4-byte file type (
note;markfor annotation files) followed by an ASCII signatureSN_FILE_VER_YYYYNNNNat offset 4. supernotelib 0.7.1 knows signatures20200001(fw C.053) through20260016(fw Chauvet 3.28.42). - [inferred] Original pre-X devices: signature
SN_FILE_ASA_20190529at offset 0, no layers, zlib (SN_ASA_COMPRESS) page bitmaps. - [verified] Body is a sequence of length-prefixed blocks (4-byte LE
length + payload). Metadata blocks are
<KEY:VALUE>strings; the last 4 bytes of the file hold the footer block's address; the footer mapsPAGE<n>,FILE_FEATURE(header), covers, titles, keywords, links to block addresses. - [verified] Each page block references up to 5 layer metadata blocks
(
MAINLAYER,LAYER1..3,BGLAYER), each pointing at an encoded bitmap (LAYERBITMAP). Ink bitmaps use theRATTA_RLEprotocol ((colorcode, length) byte pairs, grayscale color codes);BGLAYERtemplates may also be PNG (user_*page styles, [inferred]). - [verified] Page metadata carries
LAYERSEQ(z-order, top first),LAYERINFO(visibility JSON with:stored as#),ORIENTATION(1000portrait /1090horizontal),PAGESTYLE, and aTOTALPATHblock address.
Vector vs raster reality¶
Raster-first. supernotelib stores the TOTALPATH block — which is
where the device keeps per-stroke path data — as opaque bytes
(Page.get_totalpath()), and no code in 0.7.1 decodes it [verified:
grep of the installed package; manipulator.py copies it verbatim].
The library's "vectorize" option in SvgConverter/PdfConverter is
potrace bitmap tracing of the rendered page image, not pen strokes
[verified: converter.py imports potrace and traces
ImageConverter output]. So real per-point vector data (coordinates,
pressure, pen type per stroke) is not practically accessible through
supernotelib, and this reader emits no ir.Strokes.
What the reader produces instead:
- One
ir.Layerper Supernote ink layer that has bitmap content, bottom to top (reversedLAYERSEQ), each holding a full-page RGBA PNG (ir.RasterImage) rendered by supernotelib'sImageConverterwith a visibility overlay isolating that layer and hiding the background — which makes untouched pixels transparent [verified]. - Layer visibility from
LAYERINFO[verified for visible layers; hidden-layer files not exercised]. BGLAYERcontent (template/custom background), when present, becomes anir.ImageBackground[inferred — fixtures have no BGLAYER bitmap].- Pre-X non-layered pages: one flattened raster layer named
page[inferred — no legacy fixture]. page.extra["supernote"]: page id, style, orientation, and the size of the undecodedTOTALPATHblock (kept as a breadcrumb for a future true vector decoder).
Geometry¶
- [verified] Portrait canvas 1404x1872 px (A5X, A6X2 and kin);
APPLY_EQUIPMENT:N5devices (A5X2/Manta) use 1920x2560 [inferred — constant fromfileformat.py, no fixture]. - [verified] Horizontal pages (
ORIENTATION:1090) decode with swapped dimensions (1872x1404); the reader swaps page bounds to match and sets document orientation from page 0. - Coordinates: page units = device pixels, origin top-left, y down.
point_scale = 595.0 / device_width(595 pt ≈ full portrait width), so an A5X page lands at ~595x793 pt — close to letter/A4. The scale is the same constant for landscape pages (same pixel pitch).
Detection¶
detect() accepts note/mark + SN_FILE_VER_ at offset 4, or
SN_FILE_ASA_ at offset 0. Notability's .note files are PK zip
archives and are rejected [verified by test], as are reMarkable .rm
files.
Device / firmware coverage¶
- [verified] Synthetic X-series files, signature
SN_FILE_VER_20220011(Chauvet 2.5.17 era), RATTA_RLE, layered pages. - [inferred] Later signatures through
20260016parse identically;>= 20230015files switch toRattaRleX2Decodercolor codes inside supernotelib (handled transparently byImageConverter). - [unknown] Real device output quirks (multi-ink-layer notes, hidden
layers, custom
user_*templates, links/titles/keywords,.markcompanions, pre-XSN_ASA_COMPRESSfiles) — code paths exist but are untested against real captures.
Renderer gap¶
render/pdf.py does not yet draw Layer.raster (it also skips pages
with no drawable strokes), so Supernote documents currently render to
blank PDF pages. The smoke test asserts document structure and that the
PDF pipeline doesn't reject the document; raster drawing support in the
renderer is the follow-up needed for visible output.
Writer (raster MAINLAYER, 2026-07-09)¶
formats/supernote/writer.py — the fixture generator's container
machinery promoted to product code (fixtures regenerate byte-identical).
IR strokes rasterize via Pillow onto the 1404x1872 canvas (fit-contain,
centered; landscape by aspect) using render/primitives.py:stroke_runs,
quantized to the four X-series RLE codes (luminance-over-white
thresholds 64/140/217), underlay strokes first. Layer.raster PNGs
(supernote round-trips) composite directly. TOTALPATH stays undecoded so
no vector data is written — output ink is bitmap-terminal; NativeTool
does not round-trip; RAW raises. Cross-implementation check: our reader
parses the output via supernotelib. validated=False until a real
device / Partner app opens one.
Attribution¶
Container reverse-engineering and all decoding come from
supernote-tool/supernotelib by jya (github.com/jya-dev/supernote-tool),
Apache License 2.0. The test fixtures are synthetic (generated by
core/tests/fixtures/supernote/make_fixture.py) and contain no
third-party data.