System drawing canvas for images and text
  • Python 83.8%
  • Shell 16.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Bryan 0f180a856a README: self-contained wording, no pointers to other documents or working notes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 11:03:19 +01:00
examples (canvas picture): a PNG read as it is, into a surface with alpha 2026-10-24 15:22:58 +02:00
fonts canvas: sans-12 font, built and listed in the README 2026-10-25 10:20:16 +01:00
lib (canvas picture): a PNG read as it is, into a surface with alpha 2026-10-24 15:22:58 +02:00
scripts canvas: sans-12 font, built and listed in the README 2026-10-25 10:20:16 +01:00
tools scripts/bench.sh: the drawing timed, for before and after a change 2026-10-06 18:57:37 +02:00
.gitignore canvas 0.0.1: surfaces, drawing, fonts and text 2026-10-02 23:59:31 +02:00
README README: self-contained wording, no pointers to other documents or working notes 2026-10-25 11:03:19 +01:00
VERSION canvas 0.0.1: surfaces, drawing, fonts and text 2026-10-02 23:59:31 +02:00

canvas: the system's drawing and text library.

  lib/canvas/surface.talc  (canvas surface)  a rectangle of pixels, in bytes
                           of its own or at an address (a buffer a card
                           scans out); pixels one at a time; the clip
  lib/canvas/draw.talc     (canvas draw)     fill, copy, composite, mask,
                           shade, scale, turn, lines, outlines; damage
                           rectangles
  lib/canvas/font.talc     (canvas font)     a font: glyphs as coverage masks
                           with advances, bearings and kerning pairs, from
                           a .font file or a BDF; a font found by name
  lib/canvas/bdf.talc      (canvas bdf)      a BDF bitmap font, the text the
                           world distributes them in, read as it is
  lib/canvas/text.talc     (canvas text)     text on a surface: UTF-8, a pen
                           in 64ths of a pixel, kerned, every program's text
  lib/canvas/picture.talc  (canvas picture)  a picture read from a file into a
                           surface: PNG as it is, every colour type and
                           depth, the row filters undone, inflated by the
                           common library; what an icon is
  lib/canvas.talc          (canvas)          the four above under one import
  lib/canvas/protocol.talc (canvas protocol) the screen's protocol as text:
                           forms written and read back, shapes checked
  lib/canvas/buffers.talc  (canvas buffers)  pixels two programs share: a
                           file of them, mapped by both
  lib/canvas/window.talc   (canvas window)   a window on the screen, for the
                           program that owns it: connect, create, draw,
                           present, receive events
  fonts/                   plain-8x16.txt, a font drawn by hand as text, and
                           the .font the canvas loads, made from it; sans-12
                           (the built-in look's), sans-14 and sans-20,
                           proportional and anti-aliased,
                           rendered from Noto Sans (LICENSE-OFL); helv-12
                           and cour-12, the X Window System's Helvetica and
                           Courier bitmaps of 1984, pixel for pixel
                           (LICENSE-ADOBE)
  scripts/font.py          turns a drawn font into a .font
  scripts/bitmap-font.py   turns a BDF or PCF bitmap font into one, as it is
                           (a BDF also loads without it; a PCF does not)
  scripts/render-font.py   renders an outline font (TrueType) into one
  scripts/build.sh         makes the fonts
  scripts/regress.sh       runs examples/: pictures drawn and compared
  scripts/bench.sh         builds and runs tools/bench.talc: the drawing
                           timed at 960x540 and 1920x1080, for before and
                           after a change
  examples/                scene.talc and the picture it is expected to draw;
                           bdf.talc, Dina's BDF against the .font made from
                           it, glyph for glyph; protocol.talc, which checks
                           the protocol's text; picture.talc, PNGs of every
                           kind read and checked pixel by pixel, and bad
                           ones refused (scripts/picture-fixtures.py
                           writes them)

Build:

  scripts/build.sh
  scripts/regress.sh

which need the installed toolchain (compiler on PATH) and python3. A
program is compiled against the libraries by naming lib/ here on the
compiler's command line:

  compiler program.talc program /home/user/system/canvas/lib

and ../screen/tools/poster.talc is one that draws with them on a screen.
(canvas) is the drawing: a program with a window imports (canvas window)
as well, which brings the socket and the shared buffers with it, and a
program that only draws into memory or a card's buffer never does.

A WINDOW. ../screen/tools/terminal.talc is the first program with one:

  (define c (canvas-window-connect (environment-variable "SCREEN")))
  (define w (canvas-window-create c "terminal" 656 400))
  (canvas-text! (canvas-window-surface w) 8 8 font "$ " ink)
  (canvas-window-present! w (list (list 0 0 656 16)))
  (canvas-window-receive c)                ; (frame 1 time), (key 1 30 #t 0 "a" 2 time) ...

The screen that started the program put its socket in SCREEN and the
directory of fonts in SCREEN_FONTS. A window has two buffers: a present
hands one to the screen and the program draws the next frame into the
other once the frame event says so, and what the last present changed is
copied across first, so a program redraws only what changed. The buffers
are a little larger than the window (rounded up to 256 pixels each way),
so that a resize within that keeps them and the program paints only what
the new size exposes; a present names the size shown. Pixels cross
between programs through the shared files of (canvas buffers); only forms
cross the socket. canvas-window-create-translucent makes a window whose
pixels carry alpha, so what is under it shows through where it leaves
them clear, and canvas-window-popup a frameless surface hanging off a
window -- a menu, a list under a field -- that the screen takes down
(popup-done) when the pointer presses elsewhere. ../toolkit's launcher
is both. canvas-window-theme! asks the screen for another look -- a
theme file, or the built-in one -- for everything on it; the screen
then tells every program (theme path), and each reads the theme again
and draws again. canvas-window-clipboard-set! gives the screen one
text to hold for everyone, and canvas-window-clipboard-ask! asks for
it back, answered by the event (clipboard text): a clipboard that
outlives the program that copied.

ONE TEXT ENGINE. Every letter on the screen goes through (canvas text),
the screen program's decorations included, so all text looks the same and
whatever the engine learns later -- scaled outlines, kerning, shaping --
every program gets at once. Nothing about fonts crosses between programs.
The engine moves its pen in 64ths of a pixel from the start even though the
first font is a fixed grid, so that proportional and scaled fonts change
nothing above it.

FONTS are read from a directory the system hands a program (an argument
or the environment), never a path the library assumes. The first font,
plain, was drawn here for the canvas: fonts/plain-8x16.txt is the whole
thing, readable and editable with any editor, 96 glyphs of ASCII and a
replacement mark; the terminal's grid is drawn in it. The second, sans,
is Noto Sans rendered once at 14 and at 20 pixels by
scripts/render-font.py: 735 glyphs of Latin, Greek and Cyrillic with
8-bit coverage, their advances in 64ths and the 560 pairs the face kerns,
so a title bar or a label looks like type and not like a terminal. It is
not called Noto, as the Open Font License asks of a modified version;
fonts/LICENSE-OFL is the licence. dina-13, dina-15 and dina-16 are
Dina, Joergen Ibsen's terminal bitmap (MIT, fonts/LICENSE-DINA) at 8, 9
and 10 points -- cells of 7 by 13, 7 by 15 and 8 by 16 -- turned from
the BDFs under fonts/sources/ by scripts/bitmap-font.py, as helv-12 and
cour-12 are from the X Window System's Adobe bitmaps; the terminal takes
dina-13 when it is there. Where a font comes from and on what
terms is in the file, and (canvas-font-notice) answers it. Rendering
outlines at any size in talc itself is stage 3 of the plan; these are the
shape it will load into.

BITMAP FONTS, the old-school kind, are first-class and stay what they
are. A BDF -- the text format X fonts are distributed in, which most of
the world's bitmap fonts still are -- is a font the canvas loads as it
is: (canvas-font-load) takes a .bdf as it takes a .font, told apart by
their first bytes, and (canvas-font-named fonts "dina-16") finds
dina-16.font in a directory, else dina-16.bdf, so a BDF dropped into the
fonts directory is a font every program can name without a build. (canvas
bdf) reads it in about as many lines as the Python did; examples/bdf.talc
holds the two roads to the same glyphs. scripts/bitmap-font.py still
reads a BDF, or a PCF as X ships them (gzipped or not) -- PCF is binary,
and the canvas does not read it -- and writes a .font with every bit as
it was, coverage 0 or 255, advances in whole pixels, and the file FLAGGED
a bitmap font (version 3 of the format; (canvas-font-bitmap?) answers it;
a BDF loaded directly is flagged the same): the canvas's promise never to
smooth or resample such a font -- drawn at its own size or, when the
screen is scaled up, by whole numbers, so that a 1984 pixel is four 2026
pixels and not a blur. helv-12 and cour-12 are the X
Window System's Helvetica and Courier at 12 points, 75 dpi, converted by
scripts/build.sh from where Arch keeps them (X_FONTS names another
directory), 754 and 871 glyphs of ISO 10646; fonts/LICENSE-ADOBE is the
permission that comes with them, and each file's notice names it. A
theme that says (font "helv-12") and a terminal started -font cour-12
are the 1980s on a 960 by 540 screen; the hand-drawn plain font is a
bitmap font too, and is flagged the same.

A PICTURE FROM A FILE. (canvas picture) reads a PNG as it is -- the way
a BDF is read as the world distributes it -- into an argb8888 surface
with straight alpha, which canvas-composite! lays over anything: every
colour type (grey, grey with alpha, colour, colour with alpha, a
palette) at 8 bits a sample, 16 (the low byte dropped) or 1, 2 and 4;
the five row filters; tRNS as a palette's alpha or a colour key; every
chunk's CRC checked; the inflated size fixed from the header before
anything is inflated, so a file lying about its size costs nothing;
interlacing refused with a message. The pixels are made one at a time
in talc, which is right for an icon and slow for a photograph: the row
the runtime takes over when a browser wants it. ../look's icon packs
are directories of these, and (look icons) is how a program asks for
one by name.

PICTURES. The fixtures write what they draw as a binary PPM (P6) into
build/, which most image viewers open, and compare it byte for byte with
the picture under examples/. When a change is meant to look different,
look at build/*.ppm and then scripts/regress.sh -update.

SPEED. Fills, copies, compositing, masks and shades are ONE call each on
talc's %memory-* primitives, whatever the rectangle's height: the
runtime takes a width, a row count and the strides, moves eight bytes at
a time, and blends a pixel at a time in integers. At 1920x1080 a fill is
under a millisecond, a copy about one, a full-screen composite or mask
about eight (they were 766 and 584 as a call a pixel in talc), and a
1-pixel-wide line costs what a 1-pixel-tall one does (as a call a row,
a window's worth of thin lines was 22 ms). Scaling computes one row for
each source row it lands on and copies the rows that repeat it, half a
second for a full screen doubled; it is for cursors and thumbnails, not
for every frame. A turn (a piece copied by quarter turns, how a title
bar comes to read up the side of a window) is a fetch and a store a
pixel, for a strip and not a screen.