File format spec for TreeSheets .cts files, version 30. This should be enough for a programmer to write an importer/exporter.

struct File     // not really a struct, i.e. no alignment, read an element at a time
{
    char magic[4] = "TSFF";
    char version = 30;
    uchar encryption; // only if version >= 30: 0 = none, 1 = password protected, see Encryption below
    // With encryption, everything from here to the end of the file is encrypted (see Encryption).
    // TreeSheets saves the selection and the zoom level when the file is saved.
    // These three are single bytes (only present if version >= 21, and zoomlevel if version >= 23).
    uchar xs; // xs is the number of cells within the selection in x direction
    uchar ys; // ys is the number of cells within the selection in y direction
    uchar zoomlevel; // zoom level of the document
    // Only if version >= 30: exports made along with each save. Bits 0-1: HTML (0 = none,
    // 1 = with images, 2 = without images), bit 2: PDF.
    uchar autoexport;
    
    Image images[0 or more];

    char ident = 'D';   // marks start of document, and end of list of images

    // the remainder of the file from here is written as a zlib compressed stream
    Cell root;

    for (;;) {
        String tagname; // terminated by empty string
        uint cellcolor;  // only if version >= 24
        uint textcolor;  // only if version >= 24
    }
}

struct Encryption  // follows the encryption byte if it is 1
{
    uint nbblocks;    // Argon2id memory use in KiB
    uint nbpasses;    // Argon2id passes (iterations)
    uchar salt[16];   // Argon2id salt
    uchar nonce[24];  // XChaCha20-Poly1305 nonce, new for every save
    uchar mac[16];    // Poly1305 authentication tag
    uchar data[];     // the rest of the file (xs onwards) encrypted with XChaCha20-Poly1305
    // The 32 byte key is Argon2id(password as UTF-8, salt, nbblocks, nbpasses, 1 lane), as in
    // RFC 9106. The additional authenticated data is the file header from magic up to and
    // including nonce (54 bytes). Encryption is Monocypher's crypto_aead_lock, which is the
    // same as libsodium's crypto_aead_xchacha20poly1305_ietf_encrypt_detached.
}

struct Image
{
    char ident;   // marks start of image
    // Character value depends on image type, i.e. 'I' for PNG and 'J' for JPEG
    double display_scale; // display scale of image
    int64_t imagelen; // length of image (8 bytes)
    char image_data[len]; // whatever format wxImage::LoadFile uses
}

struct Cell
{
    uchar celltype;     // enum { CT_DATA = 0, CT_CODE, CT_VARD, CT_VIEWH, CT_VARU, CT_VIEWV };
    uint cellcolor;
    uint textcolor;
    uchar drawstyle;    // enum { DS_GRID, DS_BLOBSHIER, DS_BLOBLINE };
    String note;        // only if version >= 25
    uchar textalign;    // only if version >= 28; enum { TEXTALIGN_AUTO = 0, TEXTALIGN_LEFT, TEXTALIGN_CENTER, TEXTALIGN_RIGHT };
                        // automatic aligns right-to-left text right and everything else left
    uchar vertalign;    // only if version >= 29; enum { VERTALIGN_AUTO = 0, VERTALIGN_TOP, VERTALIGN_MIDDLE, VERTALIGN_BOTTOM };
                        // automatic is top, except for cells without a grid in line style, which are centered
                        // readers treat out-of-range values of both as automatic
    uchar cellcontents; // enum { TS_TEXT = 0, TS_GRID, TS_BOTH, TS_NEITHER };
    // The first cell that is within the selection is marked with the highest bit set to 1 (1 x x x x x x x) in cellcontents.
    if (TS_TEXT or TS_BOTH)
    {
        String text;
        uint relativesize;
        uint imageindex; // or 0xFFFFFFFF for no image
        uint stylebits;   // enum { STYLE_BOLD = 1, STYLE_ITALIC = 2, STYLE_FIXED = 4, STYLE_UNDERLINE = 8, STYLE_STRIKETHRU = 16 };
        uint64 lastedit;    // internal representation of wxDateTime
        TextRuns runs;      // only if version >= 27
    }
    if (TS_GRID or TS_BOTH)
    {
        uint xs, ys;
        uint bordercolor;
        uint user_grid_outer_spacing;
        uchar verticaltextandgrid;
        uchar folded;
        uint columnwidths[xs];
        Cell cells[xs * ys]; // row major
    }
}

// Rich text. The text of a cell has a base style: the cell's stylebits and textcolor. Runs mark
// ranges of the text that are styled differently; all text outside of runs has the base style.
struct TextRuns
{
    uint numruns;   // usually 0; more than the number of characters in the text is invalid
    Run runs[numruns];
}

struct Run
{
    uint start;     // position of the first code point in the cell's text
    uint length;    // number of code points, at least 1
    uint stylebits; // complete style of the run (not relative to the base style), same enum as above
    uint color;     // bits 0..23: text color, in the same encoding as the cell's textcolor
                    // bit 24 (0x1000000): set if the run has its own color, otherwise the run
                    // uses the cell's textcolor and bits 0..23 are ignored
}
// Runs must be sorted by start, must not overlap and must lie within the text. Positions and
// lengths are counted in Unicode code points (not in UTF-16 code units, and not in bytes of the
// UTF-8 string). A reader should discard runs that violate this. Readers for versions before 27
// don't know about runs, and refuse to open files of a newer version.

struct String  // as wxDataOutputStream::WriteString writes it, apparently UTF-8 prefixed by length
