ctx protocol

The ctx text format is a direct serialization of the canvas API. This protocol permits sending rendering commands over serial, ptys and other forms of network - enabling remote and/or sandboxed rendering.

When used in the ctx terminal, the ctx rendering mode is entered by issuing the escape sequence \e[?200h this makes the terminal interpret commands for a ctx context created for the line the cursor currently is in. To end ctx mode transmit  X  or  DONE .

Starting point simple printing wrappers for bash-test.sh ctx.bash, test.nim ctx2d.nim and test.py ctx2d.py provide ways to experiment with terminal apps in these languages, the bash wrapper is most complete in that it also incorporates events, none of these do images/textures yet.

The bash example ctx-clock.sh, renders an analog clock without using any binding that scales with the terminal window, for more examples of the ctx syntax in practice see the rendering test gallery.

#!/bin/bash
echo -ne "\e[?1049h" # alt-screen on
echo -ne "\e[?25l"   # text-cursor off
function cleanup {
  echo -ne " X "        # leave ctx mode; in case we're there
  echo -ne "\e[?1049l"  # alt-screen off
  echo -ne "\e[?25h"    # text-cursor on
}
trap cleanup EXIT # restore terminal state on ctrl+c and regular exit

for a in b{1..1000}; do
hour_radians=`bc <<<"scale=3;(($(date +%H|sed 's/^0//')+($(date +%M|sed s'/^6//')/60.0))/12.0+0.75)*3.14152*2"`
minute_radians=`bc <<<"scale=3;($(date +%M|sed 's/^0//')/60.0+0.75)*3.14152*2"`
second_radians=`bc <<<"scale=3;($(date +%S|sed 's/^0//')/60.0+0.75)*3.14152*2"`
radius=45
echo -ne "\e[2J\e[H" # clear screen and go to top left corner
echo -e "\e[?200h"   # enter ctx mode
_ rgba 1 1 1 0.5
arc 50% 50% $radius% 0.0 6.4 0
lineWidth $((radius/10))%
stroke
lineCap round
moveTo 50% 50%
arc 50% 50% $(($radius * 70 / 100))% $minute_radians $minute_radians 0
moveTo 50% 50%
arc 50% 50% $(($radius * 50 / 100))% $hour_radians $hour_radians 0
stroke
lineWidth $((radius/40))%
moveTo 50% 50%
arc 50% 50% $((radius * 90 / 100))%  $second_radians $second_radians 0
_ rgba 1 0.1 0.1 1
stroke
done "; sleep 0.1; done

This format maps to the C API if you turn camelCase into snake_case, and prefix each command with ctx_.

When a keyword is parsed the parser expects a series of commands of the same type, following arguments will be consumed in chunks as the number of arguments fill up, until a command change. Each command also has a short one-byte long name, where applicable these match the SVG path data format.

commandargument(s)
graygraysets gray source, and color model
grayagray alphasets gray alpha source, and color model
rgbr g bsets rgb source
rgbar g b argb alpha color source
cmykc m y kcmyk color source
cmykac m y k acmyk alpha color source
drgbr g bsets rgb source, in device space
drgbar g b argb alpha color source, in device space
dcmykc m y kcmyk color source, in device space
dcmykac m y k acmyk alpha color source, in device space
a relArcToxr yr rotation large sweep x y
b clip
c relCurveTorcx0 rcy0 rcx1 rcy1 x y
d lineDash1-Nspecify stroke dashing pattern, empty to reset
e translatex y
f linearGradientx0 y0 x1 y1
g save
h relHorLinex
i texture"eid" x ysets active texture at offset x, y - can be used after defineTexture in and in frames immediately after other frames where the texture has been used.
ka globalAlphaalpha
kb textBaselinealphabetic | top | bottom | middle | hanging | ideographic
kB blendModenormal | multiply | screen | overlay | darken | lighten | colorDodge | colorBurn | hardLight | softLight | difference | exclusion | hue | saturation | color | luminosity | divide | addition | subtract
kc lineCap capnone | round | square
ke extend extendModenone | pad | reflect | repeat
kD lineDashOffsetphasefloating point value in same units as dashes
kf fontSizefont_size
kj lineJoin joinbevel | round | miter0 1 or 2 are also valid values
kl miterLimitmiter_limit
km compositingModesourceOver | copy | clear | sourceIn | sourceOut | sourceAtop | destinationOver | destination | destinationIn | destination_out | destinationAtop | xor
kt textAlignstart | end | center | left | right
kw lineWidthline_width
kS imageSmoothing0|1Defaults to 1, when 0 nearest neighbor resampling is used.
l relLineTox y
m relMoveTox y
n font"font name"
o radialGradient6
p gradientAddStop addStoppos R G B Aarguments depend on current color model, you can change the color model without setting a color by specifying a color without arguments.
q relQuadTocx cy x y
r rectangle rectx y width height
s relSmoothTocx cy x y
t relSmoothQuadTox y
u strokeText"utf8-string"
v relVerLiney
w glyphunichardraws a single unicode character, the character no is currently passed in as decimal. (might be made internal), since a text string duplicates the API.
x fillText"utf8-string" | kerningInterspersed with utf8 sequences of text numbers can appear that shift the horizontal position.
y identity
z closePath
A arcToxr yr rotation large sweep x1 y1
B arcx y radius angle1 angle2 direction
C curveTocx1 cy1 cx2 cy2 x y
D paint
E stroke
F fill
G restore
H horLineTox
I defineTexture"eid" width height format ~a85encodedpixels~defines a texture, for now format is an integer that for 1-4 corresponds to GRAY8, GRAYA8, RGB8 and RGBA8, higher integer values correspond to the pixel formats used internally in ctx; but the protocol does as of yet not have a way to refer to pixelformat by a non-integer values.
J rotateradians
L lineTox y
M moveTox y
N beginPath
O scalescale_x scale_y
Q quadTocx cy x y
S smoothTocx cy x y
T smoothQuadTox y
U reset
V verLineToy
W transforma b c d e f g h iapplies the transformation matrix
X exit
X done
Y roundRectanglex y width nheight radius
Z closePath
] colorSpaceuserRGB|userCMYK ~a85encoded icc profile~sets the color space of a given space in rasterizer.
_ strokeSourceThe following source definition (color, gradient or texture) applies to stroking.
{ startGroupstarts a compositing group
} endGroupends a compositing group, and blends and composites it with the content before the group using the now current compositing mode, blending mode and global alpha.

Detailed syntax description

The language consists of a stream of words, numbers and strings, optionally separated by white space. The characters ,=(); are also treated as white-space. Words are substrings consisting of one or more characters in the ranges a-z and A-Z. Numbers are sequences of 0-9. Numbers can be followed by the suffixes % or @, indicating that units of percent canvas width/height should be used or a global em / cell-size. ^ is equivalent to CSS vh, and ~ vw, this can be used to override the automatic choice done by %, which favors height for dimensions that are independent of x/y like lineWidth and shadowBlur. Strings are raw binary UTF8 contained in ' or " pairs. "\n\r" etc from C are available for escaping newlines, though they can also be directly included in the string. # introduces a comment and the rest of the line is ignored.

Blobs are represented as strings using a85 ~~ as quotemarks. (optional compression makses sense to add here).

Binary variant

Internally the core datastructure of ctx is a binary variant of this protocol. Stored in chunks of 9 bytes of code + data, the various backends consume chunks of this position independent commandstream. Multiple threads do this read-only in parallel for the KMS/SDL backends.

The binary version is considered less stable, and depending on configuration options ctx uses different serialization.

Below is the start of one of the fonts in ctx in this binary form, annotated with comments.

#ifndef CTX_FONT_Roboto_Regular
/* glyph index: 
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
  jklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔ
  ÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿŁπ“”•…€™−≈␣fffiflffifflſt  */
static const struct __attribute__ ((packed)) {uint8_t code; uint32_t a; uint32_t b;}
ctx_font_Roboto_Regular[]={
{15, 0x0000a008, 0x000012e5},/* length:4837 CTX_SUBDIV:8 CTX_BAKE_FONT_SIZE:160 */
{'(', 0x00000008, 0x00000001},/* Roboto*/
{32, 0x6f626f52, 0x00006f74},
{')', 0x00000008, 0x00000001},
{'(', 0x0000004b, 0x00000009},/* Apache Licence, Version 2.0
                                Copyright 2014 Christian Robertson - Apache 2*/
{32, 0x63617041, 0x4c206568},
{'i', 0x636e6563, 0x56202c65},
{'e', 0x6f697372, 0x2e32206e},
{'0', 0x706f430a, 0x67697279},
{'h', 0x30322074, 0x43203431},
{'h', 0x74736972, 0x206e6169},
{'R', 0x7265626f, 0x6e6f7374},
{32, 0x7041202d, 0x65686361},
{32, 0x00000032, 0x00000000},
{')', 0x0000004b, 0x00000009},
{'@', 0x00000020, 0x000021dd},/*                 x-advance: 33.863281 */
{'@', 0x00000021, 0x00002333},/*        !        x-advance: 35.199219 */
{'M', 0x41c08889, 0xc2c22223},
{'l', 0xbf5ddde0, 0x428b5556},
{'4', 0x0000ffa7, 0xfdd3fff9},
{'6', 0x00000067, 0x02d6ff96},
{'8', 0xd80ee800, 0xf02bf00e},
{'8', 0x102b001c, 0x280f100f},
{'8', 0x27f11600, 0x10d510f2},
{'8', 0xf0d500e4, 0xd9f2f0f2},
{'@', 0x00000022, 0x00002bbb},/*        "        x-advance: 43.730469 */
{'M', 0x41944445, 0xc2baaaab},
{'l', 0xc0000000, 0x41be6664},
{'l', 0xc0ecccce, 0x00000000},
{'4', 0xfefa0000, 0x0000004b},