36 Curated L-Glyphs

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.

36 Curated L-Glyphs

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.

<<curated_lglyphs.janet>>=
<<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.

<<operations>>=
(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.

<<operations>>=
(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".

<<operations>>=
(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/hlineand btprnt/vline.

I'm prety sure I got the numbers right, but there was a little trial and error involved.

<<operations>>=
(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.

<<get-block>>=
(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.

<<tilemap_and_main_canvas>>=
(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.

<<layout>>=
(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.

<<glyph_and_radical_data>>=
<<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.

<<radicals>>=
(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.

<<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]

    ])