The PicoLisp Canvas library

The PicoLisp Canvas library

"This world is but a canvas to our imagination." (Hery David Thoreau)

·

6 min read

Up to now we basically only worked with different types of textfields and static elements like images. But that leaves one important field missing: How to create dynamic graphical elements?

Let's start with some basic examples. In the end, we will have a page with two canvas elements which look like this:

rectcircle.png


What is "canvas"?

The canvas element is part of HTML5 and allows for dynamic, scriptable rendering of 2D shapes. It is declared by the <canvas> tag and renders to a 300x150 px rectangle shape by default.

Typically it is used together with the JavaScript canvas library. Basically, you need to follow three steps:

  1. Find the canvas element, for example with document.getElementById,
  2. You need to define a drawing object with getContext,
  3. Then you can draw on the canvas.

An example:

<canvas id="myCanvas" width="200" height="200" />

<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.moveTo(0, 0);
ctx.lineTo(200, 100);
ctx.stroke(); 
</script>

renders a line like this:

canvasexample.png

Of course you can do more than just draw lines. Here you can find an overview over the most important 2D object methods, like defining styles and colors, transformations, text options and images.


The PicoLisp canvas.l / canvas.js library

In order to use these functions in PicoLisp, you can use the canvas.js and canvas.l library which are in the lib/ folder of the PicoLisp installation.

canvas.l basically contains two functions: a jsDraw function that calls JavaScript functions defined in @lib/canvas.js, and a <canvas> function that creates the canvas.


Let's create a simple example to understand how it works. Let's define the file: First of all, we need to load the libraries, define our canvasTest function which calls (html), and then start the server.

(load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l" "@lib/canvas.l")

(de canvasTest ()
   (and (app) *Port% (redirect (baseHRef) *SesId *Url))
   (action
      (html 0 "Canvas Test" "@lib.css" NIL ) ) )

(server 8080 "!canvasTest")

If we execute it now with $ pil <filename>.l +, we see an empty page on localhost:8080. Now let's fill it up.


The <canvas> function

The <canvas> takes four parameters:

  • an internal symbol for the canvas ID,
  • two numeric values for x and y value,
  • and an optional string that is printed if <canvas> is not supported by the browser.

In order to pass an internal symbol via HTTP to the server, it should start with $ to mark it as internal symbol. The $ itself is part of the encoding, not of the symbol name.


Let's add it to our html function:

(html 0 "Canvas Test" "@lib.css" NIL
   (<canvas> "$testID" 400 300 ) ) ) )

If we visit the page now, it still looks the same. But if we check the source-code (Ctrl-U in Firefox), we see a HTML canvas element inside the <body> tags:

<body>
<canvas id="$testID" width="400" height="300" onmousedown="csMouseDn(this, event)" ontouchstart="csTouchDn(this, event)" onmousemove="csMouseMv(this, event)" ontouchmove="csTouchMv(this, event)" onmouseup="csMouseUp(this)" ontouchend="csTouchEnd(this)" onmouseout="csLeave(this)" ontouchleave="csLeave(this)" class="canvas"></canvas>
</body>

Let's fill it with some shapes.


The drawCanvas function

The canvas content is drawn by defining a drawCanvas function (the name is mandatory). It takes two arguments:

  • the ID of the <canvas> element,
  • and a numeric "delay" value in milliseconds. For a delay <0, the canvas content is only drawn once, otherwise it is auto-drawn repeatedly.

drawCanvas returns a list of commands that can be built for example with the make function.

For example, let's draw a red rectangle:

(de drawCanvas (Id Dly)
   (make
      (csStrokeStyle "blue")
      (csStrokeRect 0 0 400 300)
      (csFillStyle "red")
      (csFillRect 100 100 200 100) ) )

These functions call the corresponding JavaScript functions strokeStyle, strokeRect, fillStyle and fillRect (JavaScript examples for example on w3schools.com).


Adding onload=drawCanvas

Now we need to tell the browser that the JavaScript for the canvas elements needs to be loaded. Usually we want to execute it directly when the page is loaded. We have two possibilities to do this:

  • In the html function to insert it directly into the <body> tag:
(html 0 "Page Title" "style.css" '((onload . "drawCanvas('$testID', -1)"))
  • In the page body, with help of the javascript function:
(javascript NIL "onload=drawCanvas('$testID', -1)")

Either way the drawCanvas function from the canvas.jslibrary gets called, and executes the commands listed in the PicoLisp drawCanvas function.


Test

Now let's start the script and point it to localhost:8080. It looks like this:

canvastest.png

You can find the full source code of this example here.


Working with internal symbols

Up to now, we treated the ID just like a plain string. But actually it is a symbol, which opens many powerful possibilities!

As you might know, objects are internal symbols as well. So how about if we assigned an object to our drawCanvas function? (Spoiler: it works!)


Let's define a class +Rectangle. It should have the properties x, y, dx, dy, stroke and fill, and a method draw>.

(class +Rectangle)
# x y dx dy stroke fill

(dm draw> ()
   (csRect (: x) (: y) (: dx) (: dy))
   (csStrokeStyle (: stroke))
   (csFillStyle (: fill)) )

Then we create an object rectangle:

object 'rectangle '(+Rectangle)
   'x 100
   'y 100
   'dx 200
   'dy 100
   'stroke "blue"
   'fill "red" )

Now we can assign this object to <canvas> and drawCanvas.

(<canvas> "$rectangle" 400 300)  
(javascript NIL "onload=drawCanvas('$rectangle', -1)")

The $ is marking "rectangle" as an the internal symbol.

Now we can simplify drawCanvas to:

(de drawCanvas (Id Dly)
   (make
      (draw> Id)
      (csStroke)
      (csFill) ) )

draw> defines position and colors of stroke and fill. Now we only need to call the draw> method of the object, and csStroke and csFill to actually draw it.

You can find the full source code of this example here.


Adding a Second Canvas Element

This approach opens up many possibilities: We can boil very complex examples down to the dynamic behaviour of objects, and we can create several instances of <canvas> on the same page.

Let's say we want to have a circle canvas on its own canvas: Then we only need to define the object.

(class +Circle)
# x y r a b stroke fill

(dm draw> ()
   (csArc (: x) (: y) (: r) (: a) (: b))
   (csStrokeStyle (: stroke))
   (csFillStyle (: fill)) )

(object 'circle '(+Circle)
   'x 200
   'y 150
   'r 75
   'a 0
   'b "6.2832"
   'stroke "green"
   'fill "blue" )

There is no need to redefine the drawCanvas function, because it is all covered by the circle draw> method.

So we are free to include canvas anywhere on our page:

#  Rectangle
(<h1> NIL "This is a rectangle")
(<canvas> "$rectangle" 400 300)
(javascript NIL "onload=drawCanvas('$rectangle', -1)")
# Circle
(<h1> NIL "This is a circle")
(<canvas> "$circle" 400 300)
(javascript NIL "onload=drawCanvas('$circle', -1)") ) ) )

And this is the result:

rectcircle.png


You can find the full source code of this example here.


Sources

irasutoya.com/2019/09/blog-post_35.html
w3schools.com/graphics/canvas_coordinates.asp
en.wikipedia.org/wiki/Canvas_element
developer.mozilla.org/en-US/docs/Web/API/Ca..