36 Curated L-Glyphs
1. Overview
I am working on a notation system for these Gestlings, and this is my very first initial (digital) experiment.
The style of notation is something I'm inclined to call
brutalist notation
, because it is optimized for the
digital medium it lives on: pixels.
Below are 36 curated L-Glyphs
, so called because of their
funny L shape. These are components of a larger
whole, which is why they have the shape that they do.
An L Glyph is made up of 4 4x4 pixel tiles called
radicals
. Radicals can be mixed and matched to create
potentially hundreds or possibly thousands of combinations,
but the idea is to find ones that are visually distinct and
aesthetically pleasing.
These initial 36 were done by hand. While I wasn't trying to design these to be easy to write, I found it was trivial to turn many of the symbols I had in mind into strokes.
These symbols don't mean anything yet. Eventually, they will turn into a number system used to represent the duration of a particular segment of a gesture in numerator-denominator format.
2. Some Thoughts
These L-Glyphs are forms thaat will eventually eventually be used to represent numbers for the scaling factor of a gesture target value, so my mind was initially in the mode of counting systems.
I was just working with Mayan counting systems, and had a modification that extended the base and allowed more numbers. But it turns out I didn't even have enough pixel space for that (it could be done with 8x8 pixels, but even that was too much to ask for). The "L" shape came into being because it was the only space that was available.
The first row of glyphs, I was thinking about counting systems. Making some glyphs that could resemble one, two, three, four, five, and six.
The second row, I decided to relax a little bit on trying to make numbers, and just improvised a little bit, seeing what various radical combinations could look like.
I return back to thinking about number systems in row 3, with the idea that glyph could be split into two groups of two radicals, with one group acting like the MSBs, and the other the LSBs. I am messing around with this duality here.
In row 4, I am reminded that the "cap" radical exists, and begin playing with that. I am also thinking about the triplet grouping involving the corner, and how nice and symmetrical these forms look.
Row 5 revisits the idea of the 2-radical shape.
By row 6, I am out of ideas. So I squeezed a few weird ones out.
3. Tangled Source
This is the top-level entry point, depicting the main program code. All the abstractions and data leading up to this point have been abstracted away.
To be tangled using worgle. If you have the source code,
this can be done
with worgle curated_lglyphs/curated_lglyphs.org
.
This program builds up a proof-of-concept interterface
for procedurally generating L-Glyphs. 36 are stored in
an array called glyphs
, and the visible code below
takes these glyphs and places them on a 6x6 layout. An
L-shaped outline is drawn between each one to show the
bounds.
<<operations>>
<<tilemap_and_main_canvas>>
<<layout>>
<<glyph_and_radical_data>>
(for y 0 boxrows
(for x 0 boxcols
(def glyphreg (get-block center x y))
(def glyphpos (+ (* y boxrows) x))
(if (< glyphpos (length glyphs))
(do
(def gl (glyphs glyphpos))
(draw-glyph bp tilemap glyphreg gl)
(outline-glyph bp glyphreg)))))
(def geneva9 (btprnt/macfont-load "fonts/geneva_9"))
(btprnt/macfont-textbox
bp geneva9
main
8 (- 256 17) "36 curated L-glyphs" 1)
(print "<img src=\"data:image/png;base64,")
(print (btprnt/write-png bp))
(print "\" alt=\"36 Curated L-Glyphs\">")
(btprnt/del bp)
(btprnt/del tilemap)
4. Operations
A number of key operations are constructed to created these glyphs in a procedural way. These build on top of the BTPRNT 1-bit graphics library.
4.1. Load a Tilemap
BTPRNT provides tile map functionality for drawing with tiles, but does not have any great means of drawing the tile map itself.
The solution here is to write a janet function that parses
a text file containing a tile map. This allows tile maps to
be constructed using a text editor. The function for this
is called loadbuf-v2
. Version 1 (not here)
originally comes from the Candy Crystal Rainbow Codex code.
It has been improved and now works with arbitrary sized
tile sizes. In this case, our tiles are 4x4.
The output of this is a Janet buffer containing raw bitmap. After it is transferred to a btprnt buffer, it can be used as a tilemap.
(defn loadbuf-v2 [filename gwidth gheight cols rows]
(var xpos 0)
(var ypos 0)
(def onbit ((string/bytes "#") 0))
(def offbit ((string/bytes "-") 0))
# btprnt usually does this automatically
# handle non-multiples of 8
(def stride
(let (w (* gwidth cols))
(if (= (% w 8) 0)
cols
(+ cols 1))))
(def height (* rows gheight))
(var buf (buffer/new-filled (* stride height)))
(var f (file/open filename :r))
(var linepos 0)
(defn pixel [x y s]
(var off (math/floor (/ x 8)))
(var pos (+ (* y stride) off))
(var bitpos (- x (* off 8)))
(if (= s 1)
(set (buf pos)
(bor (buf pos) (blshift 1 bitpos)))
(set (buf pos)
(band (buf pos) (bnot (blshift 1 bitpos))))))
(loop [line :iterate (:read f :line)]
(var a (string/bytes line))
(if (or (= (a 0) onbit) (= (a 0) offbit))
(do
#(prin (string line))
(if (>= linepos gheight)
# end of glyph. get ready for next glyph.
(do
(set linepos 0)
(set xpos (+ xpos 1))
# possibly go to new road if at the end
(if (>= xpos cols)
(do
(set xpos 0)
(set ypos (+ ypos 1))))))
# add bits to row, then get ready for next row
(for i 0 (length a)
(cond
(>= i gwidth)
'()
(= (a i) offbit)
(pixel
(+ (* xpos gwidth) i)
(+ (* ypos gheight) linepos)
0)
(= (a i) onbit)
(pixel
(+ (* xpos gwidth) i)
(+ (* ypos gheight) linepos)
1))
)
(set linepos (+ linepos 1)))))
(file/close f)
buf)
4.2. Draw Radical
A radical is a single 4x4 tile from the tile map. It is
drawn using the function draw-radical
.
To
draw a radical is to draw a tile using the btprnt function
btprnt/tile
.
Tiles are located using a (column, row)
convention, which gets turned into a 2-element array with
Janet called rad
.
Note that a 2x scaling factor is used. This makes the L glyphs a bit more readable.
(defn draw-radical [bp map reg rad x y]
(btprnt/tile
bp
map
reg
(+ x 2) (+ y 2)
(rad 0) (rad 1)
4 4
2 1))
4.3. Draw Glyph
A single L-glyph is drawn with draw-glyph
.
This draws and places 4 radicals, and therefore makes
4 calls to draw-radical
.
A glyph is represented as a 4-element array of radicals. They are ordered top down left to right, with the first element being the topmost radical, and the last element being the radical that forms the "L".
(defn draw-glyph [bp tilemap reg glyph]
(draw-radical bp tilemap reg (glyph 0) 0 0)
(draw-radical bp tilemap reg (glyph 1) 0 8)
(draw-radical bp tilemap reg (glyph 2) 0 16)
(draw-radical bp tilemap reg (glyph 3) 8 16))
4.4. Outline Glyph
The outline-glyph
function draws an outline around
a 2x scaled L-glyph with some padding. Horizontal
and vertical line primitives are used via btprnt/hline
and btprnt/vline
.
I'm prety sure I got the numbers right, but there was a little trial and error involved.
(defn outline-glyph [bp reg]
(btprnt/hline bp reg 0 0 12 1)
(btprnt/hline bp reg 0 27 20 1)
(btprnt/vline bp reg 0 0 27 1)
(btprnt/vline bp reg 11 0 17 1)
(btprnt/hline bp reg 11 16 8 1)
(btprnt/vline bp reg 19 16 11 1))
4.5. Get Block
The get-block
function produces a region to draw an
L-glyph, given its column and row position in the layout.
The draw-glyph
function can then draw in a local
coordinate space, while allowing the results to appear in
the right spot globally.
This function is location-dependent because it implicitely
uses the boxwidth
and boxheight
definitions.
(defn get-block [reg col row]
(array
(+ (reg 0)
(* (+ boxwidth padding) col))
(+ (reg 1)
(* (+ boxheight padding) row))
boxwidth
boxheight))
5. Tilemap and Main Canvas
It is at this part of the code that we create the initial tilemap and main canvas to draw on.
Both the tilemap and main canvas are their own instances of btprnt.
The tilemap is generated first by loading the radicals
into a janet buffer via loadbuf-v2
, then writing it
to the btprnt instance tilemap
via btprnt/drawbits
.
(def buf (loadbuf-v2 "curated_lglyphs/radicals.txt" 4 4 8 8))
(def tilemap (btprnt/new 64 64))
(btprnt/drawbits tilemap buf
@[0 0 64 64]
0 0 64 64
0 0)
(def bp (btprnt/new 256 256))
6. Layout
The layout of this drawing is a 6x6 array of L-glyphs. These get arranged with a bit of padding in between, and are all made to be centered around the main canvas.
(def main @[0 0 256 256])
(def padding 8)
(def boxwidth 20)
(def boxheight 28)
(def boxrows 6)
(def boxcols 6)
(def total-width
(+ (* boxwidth boxcols) (* padding (- boxcols 1))))
(def total-height
(+ (* boxheight boxrows) (* padding (- boxrows 1))))
(def center (btprnt/centerbox bp main total-width total-height))
<<get-block>>
7. Glyph and Radical Data
Enough abstraction has been built up in Janet so that glyphs and underlying radicals components can be expressed in a human readable way. The curated glyphs are stored as data below.
<<radicals>>
<<glyphs>>
7.1. Radicals
Radicals are small 4x4 pixel components that build up an L-Glyph. These have been typed up in a text file, and loaded up into memory as a tilemap. The relative locations are stored below, with human readable names.
Radicals can be found in the
file curated_lglyphs/radicals.txt
.
empty
is a an empty box. Nothing in it.
dot
produces a "dot" in the center.
A box
is the inverse of a dot
, which creates an
outline around the edges.
vline
produces a vertical line.
tee
makes a T shape. There's also rtee
, ltee
, and
btee
for tees skewed, right, left, and bottom,
respectively.
knee
creates L-shaped Knee, intended for the corner.
block
is the opposite of empty
everything is on,
creating a big blob. This was used for making layouts
and borders were done correctly, and shouldn't actually
be used.
A cap creates little brackets. lcap
, rcap
, tcap
,
and bcap
stand for left, rigth, top, and bottom caps.
A stub is a half-line, intended to be used in situations
where you want a line to end uneventfully. lstub
,
rstub
, tstub
, and bstub
refer to left, right, top,
and bottom stubs.
(def empty @[0 0])
(def dot @[1 0])
(def box @[2 0])
(def vline @[3 0])
(def tee @[4 0])
(def knee @[5 0])
(def rtee @[6 0])
(def block @[7 0])
(def ltee @[0 1])
(def tstub @[1 1])
(def bstub @[2 1])
(def btee @[3 1])
(def lstub @[4 1])
(def tcap @[5 1])
(def rcap @[6 1])
(def rstub @[7 1])
(def lcap @[0 2])
(def bcap @[1 2])
7.2. Glyphs
A glyph is represented as a 4-element array containing
its radicals in the following order: top, middle, bottom,
right. These glyphs are stored inside of an array called
glyphs
.
(def glyphs
@[
# row 1
@[tstub vline bstub empty]
@[dot empty dot empty]
@[dot dot dot empty]
@[dot dot dot dot]
@[empty empty box empty]
@[empty vline box empty]
# row 2
@[empty dot box dot]
@[empty vline knee rtee]
@[tee vline bstub empty]
@[box box box box]
@[tee vline knee rtee]
@[box vline knee rtee]
# row 3
@[empty empty ltee rtee]
@[empty empty box box]
@[dot empty box box]
@[empty vline btee box]
@[dot dot lstub rtee]
@[box vline knee dot]
# row 4
@[empty tcap knee rcap]
@[empty dot bcap empty]
@[tstub vline knee dot]
@[empty dot knee dot]
@[empty box dot box]
@[empty box dot dot]
# row 5
@[empty tcap dot rcap]
@[empty empty lstub rstub]
@[empty empty ltee rcap]
@[empty tcap btee empty]
@[empty tcap knee rcap]
@[box empty box empty]
# row 6
@[box empty empty dot]
@[box box lcap dot]
@[dot empty lstub rstub]
@[tstub vline bstub dot ]
@[empty empty lstub box]
@[tcap vline dot dot]
])