The .drawo container
Drawoble saves a drawing as a single .drawo file. This document
describes the container it is wrapped in — the archive format, the entry names,
the bytes an identification tool matches on, and where the version number lives.
It does not describe the drawing itself.
Short version. A .drawo is a ZIP archive whose first
entry is an uncompressed file named mimetype containing the string
application/vnd.drawoble.drawing+zip. That puts the media type at a
fixed byte offset, which is what lets a tool tell a Drawoble drawing apart from
any other archive.
1. Identity
| Property | Value |
|---|---|
| Media type | application/vnd.drawoble.drawing+zip |
| File extension | .drawo |
| Structured syntax suffix | +zip, registered by RFC 6839 |
| Parameters | None, required or optional |
| Specification | This document |
| Encoding | Binary; text entries inside are UTF-8 without a byte order mark |
The name sits in the vendor tree described by RFC 6838 section 3.2, in
producer-then-product form, so a later Drawoble format registers as a readable
sibling rather than beside a bare vendor name carrying no product segment. The
+zip suffix is accurate rather than decorative: the container genuinely
is a ZIP archive, and a tool that already knows how to look inside one loses nothing
by treating a .drawo the same way.
2. Container layout
A .drawo is a ZIP archive with its central directory at the end of the
file, as ZIP requires. Every entry except the first is DEFLATE-compressed. Entry
names are the following, and no entry name is ever localised or renamed:
| Entry | Required | Compression | Contents |
|---|---|---|---|
mimetype |
Yes, and first | Stored | The media type string, with no trailing newline. This entry carries no payload; it exists so the file can be identified. |
manifest.json |
Yes | Deflate | Container metadata and the integrity anchor. See section 4. |
drawing.json |
Yes | Deflate | The drawing itself. Its schema is out of scope for this document. |
thumbnail.png |
No | Deflate | A preview image, at most 512 by 512 pixels. Regenerable and never authoritative — a reader that ignores it loses nothing. |
assets/<id>.<ext> |
No | Deflate |
Embedded binaries referenced by the drawing. PNG, JPEG and WebP take their
own extension; every other media type is written .bin.
|
Entries are written in that order. Nothing outside the list above is written, and a reader is expected to ignore an entry it does not recognise rather than reject the file. Payloads are not compressed a second time inside the archive: the ZIP layer is the only compression, because doubling it costs processor time and saves nothing.
An asset's extension is the only record of its media type: neither the manifest nor
the drawing carries one beside it. The mapping is deliberately small.
image/png, image/jpeg and image/webp are
written .png, .jpg and .webp; every other
media type is written .bin. Reading inverts the same table, also
accepting .jpeg as image/jpeg, and reports an extension it
does not know as application/octet-stream. A media type outside those
three therefore does not survive a round trip, which is worth knowing before
embedding one.
3. Byte signature
A ZIP local file header is thirty bytes of fixed-width fields followed by the entry
name and then the entry's data. Because the mimetype entry is written
first, is stored rather than compressed, and carries no extra field, three positions
near the start of every file Drawoble writes are constant:
| Offset | Length | Bytes | Meaning |
|---|---|---|---|
| 0 | 4 | 50 4B 03 04 |
The ZIP local file header signature |
| 30 | 8 | mimetype |
The first entry's name, immediately after the fixed header |
| 38 | 36 | application/vnd.drawoble.drawing+zip |
That entry's content, stored uncompressed |
The three positions must be matched together. The four bytes at offset 0 belong to every ZIP archive in the world, so a rule that matches them alone claims every archive on the machine. What identifies a Drawoble drawing is the media type at offset 38, and offsets 0 and 30 are what make that offset predictable.
Offset 38 holds only while the first entry carries no extra field, because a ZIP writer places extra-field bytes between the entry name and its content. Some writers add alignment or timestamp extra fields by default. Drawoble does not, so the three positions above describe every file Drawoble writes today, and they are what an identification rule should be cut from.
A file that does not match is not thereby something other than a
.drawo. The mimetype entry was added after the format had
already shipped, so drawings written before it carry no anchor at all, and their
first entry is a DEFLATE-compressed one like any other. Drawoble opens those exactly
as it always did, without a warning, and they gain the anchor the next time they are
saved — there is no rewriting pass, so one can sit on disk indefinitely. To an
identification tool such a file is an ordinary ZIP archive, which is the accepted
price of not having had the anchor from the beginning. What a reader should refuse is
the other case: an anchor that is present and names a different media type.
Here are the first eighty bytes of a real file. Everything shown is identical in every file Drawoble writes except the four bytes at offsets 10 to 13, which are the entry's modification timestamp in MS-DOS form. The checksum at offsets 14 to 17 looks variable but is not: it is computed over the media type string, which never changes.
00000000 50 4b 03 04 14 00 00 00 00 00 00 60 e7 5c 4c 05 |PK.........`.\L.| 00000010 0c 62 24 00 00 00 24 00 00 00 08 00 00 00 6d 69 |.b$...$.......mi| 00000020 6d 65 74 79 70 65 61 70 70 6c 69 63 61 74 69 6f |metypeapplicatio| 00000030 6e 2f 76 6e 64 2e 64 72 61 77 6f 62 6c 65 2e 64 |n/vnd.drawoble.d| 00000040 72 61 77 69 6e 67 2b 7a 69 70 50 4b 03 04 14 00 |rawing+zipPK....|
4. The manifest
manifest.json is a JSON object with exactly the following fields, in
snake case. Unknown fields are rejected rather than ignored — the manifest is the
container's integrity anchor, and a reader that guessed at it would be guessing
about whether the rest of the file is intact.
| Field | Type | Meaning |
|---|---|---|
version |
Integer, currently 1 |
The layout version of the container itself — the entry names and this manifest's own shape. |
creator |
String | Free text identifying the writer, for diagnostics. Never parsed. |
schema_version |
Semantic version |
The version of the drawing payload. Independent of version, and
the subject of section 5.
|
checksum |
sha256: and 64 lowercase hexadecimal digits |
SHA-256 over the exact uncompressed bytes of drawing.json.
|
created_at |
RFC 3339 timestamp | When the file was written, in UTC. |
The checksum covers drawing.json and nothing else — not the manifest,
not the thumbnail, and deliberately not the mimetype entry. Widening it
would redefine the field and invalidate every file written before the change, and the
archive's own per-entry checksums already cover those bytes. The
mimetype entry is an aid to identification, never a trust boundary:
anyone able to edit it is equally able to recompute the checksum beside it.
5. Versioning
The media type declares no parameters, and it never will. There is
no version parameter to read and none to write. A file written by a
much later release of Drawoble is still
application/vnd.drawoble.drawing+zip, and an identification rule built
on the bytes in section 3 keeps matching it.
The version lives inside the file instead, as two independent numbers.
version describes the container — the entry names above and the shape of
the manifest — and changing it would be a change to this document.
schema_version describes the drawing payload, moves with ordinary
product releases, and is expected to. Reading one to learn about the other is a
mistake.
The payload version is a semantic version, and Drawoble's own reading rules follow from it: a file whose major version matches the reader opens, with a warning if it was written by a newer minor release. A file from a different major version is not opened by guesswork. None of that is a constraint on the container, and a third-party tool is free to take a different view of a payload it understands.
Room for payload growth is one named region rather than a general tolerance, and it
is the one part of the drawing this document has to name. The optional
extensions object is that region: keys Drawoble does not understand are
carried through a load and written back out unchanged, so what one release cannot
interpret still survives a round trip through it. Everywhere else the payload is
strict — a field the reader does not recognise is refused rather than ignored, and
the file does not open at all. Anything a third-party writer wants kept, and cannot
expect Drawoble to know about, therefore belongs under extensions rather
than beside the fields it already knows.
6. What this document does not cover
The schema of drawing.json is not published. It is Drawoble's, it
changes with the product, and nothing about identifying a file requires it. The same
is true of the contents of embedded assets, which are ordinary images carrying their
own formats. A tool that wants to know a file is a Drawoble drawing has everything it
needs in sections 2 and 3; a tool that wants to read the drawing does not, and that
is deliberate rather than an omission.
The container defines no encryption, no external references, no scripting and no
macros. Everything a .drawo contains is data, and opening one executes
nothing. A reader handling untrusted files should apply the usual archive
precautions — bound the decompressed size, and refuse an entry name that escapes the
directory it is extracted into.