The Decker File Format

Decker is a multimedia platform for creating and sharing interactive documents, with sound, images, hypertext, and scripted behavior. This guide describes the structure of Decker documents in detail, to facilitate the creation of compatible document viewers and editors.

General Concepts

Decker documents are called Decks, and consist of a series of Cards. Cards in turn contain Widgets. The Decker environment can broadly be in "editing" mode, where widget properties are manipulated interactively, and an "interaction" mode where widgets respond to clicks and other interactions by emitting events and running scripts as applicable.

Widgets, cards, or the deck itself may contain scripts written in the Lil programming language. Scripts have a mechanism for "bubbling" events up along the container hierarchy from a source widget to its containing card, and perhaps to the deck itself. In response to events, Lil scripts can inspect and manipulate properties of the deck via a series of Interfaces furnished by the application. For more detail on this, see the Decker manual. Lil scripts are not persistent- their local variable bindings are discarded between events. Everything that is persistent about a deck- especially widget values- is captured in the representation described in this guide.

Lil code intended to be reusable across projects can be organized into Modules. From a programming perspective, modules execute once when a deck is loaded (or the module is modified) and each return a dictionary which will be mounted as a global for use by ordinary event-triggered scripts. Modules do not have direct access to the deck or its components, nor do they have direct access to one another. Each module has an independent key-value store which can furnish static data used by the module or allow the module to explicitly preserve its state.

In addition to the built-in selection of widgets, it is possible to define custom widgets called Contraptions. Internally, a contraption behaves like a card, containing widgets (representing its state and interactive elements) and a background image. Externally, it behaves like a single widget. Many contraption instances can be created from a single definition, which is called a Prototype.

Rationale

The deck format is designed around several constraints and considerations:

Wrapper

Decks are stored in UTF-8 encoded text files (optionally with a UTF-8 BOM), and have the following structure:

<body><script language="decker">
PAYLOAD
</script>
RUNTIME

Where PAYLOAD is the format described in the remainder of this document and RUNTIME is a self-contained sequence of HTML, CSS, and JavaScript sufficient for making the deck self-executing. Anything which is not a web browser can ignore the content of the document following the </script> tag that terminates PAYLOAD.

The PAYLOAD must never contain the literal sequence of characters </script: a web browser would incorrectly treat this as a premature end to the enclosing script tag and thus the payload, corrupting the document.

Applications must support reading documents consisting only of the PAYLOAD section, with no surrounding tags or RUNTIME, for environments where a web browser is not available/desired or when the overhead of the RUNTIME stub is inconvenient. Such a document should be stored with a .deck extension.

Applications must tolerate Windows-style newlines (CR-LF), interpreting them identically to Unix-style newlines (LF only). Emitting Unix newlines when creating documents is strongly recommended.

Data Blocks

The payload may contain fields this guide calls data blocks. Data blocks provide a representation of arbitrary binary data which fits in a JSON string, and has less overhead than a JSON array of numbers. Since APIs for copying and pasting non-textual data are highly heterogenous between operating systems and application frameworks, and many browsers provide limited-to-nonexistent support for this functionality, Decker uses the same data block formats to represent non-textual data in the system clipboard.

Data blocks have a structure like %%TTTFP...:

In the future, the official Decker file format may support additional formats for some block types. If independent forks wish to introduce their own formats it is strongly recommended that they begin with format Z and work backwards alphabetically, to reduce the chance of clashes.

The data block types used by Decker are as follows:

Format Width Cast Range Description
DAT0 1 "u8" 0...255 unsigned 8-bit int
DAT1 1 "i8" -128...127 signed 8-bit int
DAT2 2 "u16b" 0...65535 unsigned 16-bit int, big-endian
DAT3 2 "u16l" 0...65535 unsigned 16-bit int, little-endian
DAT4 2 "i16b" -32768...32767 signed 16-bit int, big-endian
DAT5 2 "i16l" -32768...32767 signed 16-bit int, little-endian
DAT6 4 "u32b" 0...4294967295 unsigned 32-bit int, big-endian
DAT7 4 "u32l" 0...4294967295 unsigned 32-bit int, little-endian
DAT8 4 "i32b" -2147483648...2147483647 signed 32-bit int, big-endian
DAT9 4 "i32l" -2147483648...2147483647 signed 32-bit int, little-endian
DAT: 1 "char" n/a ASCII character

Payload

The PAYLOAD consists of a series of lines. Except in {script:ID} chunks, empty lines and lines beginning with a # are ignored as comments, giving users flexibility in annotating documents as they wish. Applications are not required to preserve comments.

A Chunk begins with a line that begins and ends with curly braces {}, indicating the type of the chunk, and spans until the beginning of the next chunk. Some chunk types have an ID which is separated from the chunk type with a colon (:). The following chunk types exist:

Except in {script:ID} chunks, non-comment lines within a chunk are Property Lines consisting of an ID followed by a colon (:). The remainder of the line is interpreted as JSON data. JSON data must escape all forward-slash characters (/ as \/). For example:

{deck}
version:1
name:"AC\/DC Fan Zine"
size:[320,240]
card:4

{script:ID} chunks contain verbatim Lil code and are always terminated by an {end} on its own line. For example:

{script:3}
# this is a Lil comment
on click do
  alert["You clicked a thing!"]
end
{end}

Chunks permit flexible ordering within a document, but often their relative ordering is significant:

IDs and the contents of a {script:ID} chunk can contain nearly any printable ASCII characters. To avoid ambiguity, the following plain characters can be replaced with equivalent escaped sequences of characters:

Plain Escaped Notes
{ {l} Always escape this character.
} {r} Always escape this character.
: {c} Always escape this character in IDs.
/ {s} Escape this character if it is immediately preceded by <.

The {deck} Chunk

The Deck chunk contains a number of optional properties with metadata pertaining to the entire document.

The {card:ID} Chunk

Card chunks always have an ID, which serves as the name of the card. They also may have additional optional properties:

An example of a {card:ID} chunk complete with {widget} section:

{card:home}

{widgets}
one:{"type":"button",text:"Click Me!"}
two:{"type":"button",text:"Check Me Out","style":"check","value":1}

The {widgets} Chunk

The {widgets} chunk is a dictionary of widgets belonging to the preceding {card:ID} or {contraption:ID} chunk, in their drawing order, back-to-front. Each widget is a JSON object with one or more properties.

Some properties are common to all widgets:

Each type of widget has its own additional optional fields:

The {module:ID} Chunk

Module chunks always have an ID, which serves as the name of the module. They also may have additional optional properties:

Modules may contain a {data} chunk containing a dictionary of names associated with JSON values, in the same structure as {widgets} or {fonts}.

Module properties and/or data are always followed immediately by a {script} chunk (with no ID) representing the module body.

An example of a complete {module:ID} chunk:

{module:logger}
description:"a utility module for logging"
version:1.0
{data}
log:{time:[],message:[]}
{script}
log:table data.log

mod.put:on _ x do
 log:insert time message with sys.now x into log
 data.log:cols log
 log
end

mod.get:on _ do
 log
end
{end}

The {contraption:ID} Chunk

Contraption chunks always have an ID, which serves as the name of the Prototype. They may have additional optional properties:

Attribute types may be one of the following:

An example of a complete {contraption:ID} chunk and its associated {widgets} and {script:ID} chunks:

{contraption:spinner}
size:[150,50]
description:"unbounded numeric field that can be incremented or decremented by a configurable step"
attributes:{"name":["value","step"],"label":["Value","Step"],"type":["number","number"]}
script:"spinner.root"

{widgets}
down:{"type":"button","text":"<","script":"spinner.0"}
up:{"type":"button","text":">","script":"spinner.1"}
value:{"type":"field","value":"0","locked":1}
step:{"type":"field","value":"1","show":"none"}

{script:spinner.root}
on set_value x do
  value.text:0+x
end
on get_value do
  0+value.text
end
on set_step x do
  step.text:0+x
end
on get_step do
  0+step.text
end
{end}

{script:spinner.0}
on click do
  value.text:value.text-step.text
end
{end}

{script:spinner.1}
on click do
  value.text:value.text+step.text
end
{end}

An Example Deck

The following is a simple structurally valid deck, with ... elisions for clarity:

<body><script language="decker">
{deck}
version:1
size:[320,240]
card:0

{card:homeCard}
{widgets}
pusher:{"type":"button","text":"Click Me!","script":0}

{script:0}
on click do
  alert["you clicked the button.\nhooray for you."]
end
{end}

{fonts}
body:"%%FNT0CAoBAQAAAAA...AAAAAqAAA"
bold:"%%FNT0EA0BAgAAAAA...AAAAAAAAA"

</script>
...

Changelog

1.0:

1.2:

1.10:

1.12:

1.23:

1.25:

1.41:

1.43: