Literate Programming 6 - Integrated Tutorials.

Prev:WEB 5 Top:WEB 0 Next:WEB 7

Prologue.

Jon Breuer - September 16, 2024.

I think the previous article went well, but there were some downsides. I'm not writing these articles from scratch, I'm iterating on them. Being able to see the code is nice, but it lacks context of where I'm making each change. Also, each section tends to be a long series of blocks of text followed by a visually unconnected image or canvas.

I'm thinking of Bret Victor's great website: Worry Dream

This is the source *.WEB file, and this is the generated HTML.

This would be the code area.
string formatCodeForDisplay(string source, int lineNumber) {
string output = "";
string scanner = escapeHTMLCharacters(source);
scanner: while(!scanner.empty) {
if(scanner.startsWith("//")) {
// Color comments. output ~= "<span class=\"code_comment\">";
int lineLength = countUntil(scanner, "\n");
if(lineLength < 0) { lineLength = scanner.length; }
output ~= scanner[0..lineLength - 1];
output ~= "</span>";
scanner = scanner[lineLength..scanner.length]; } else if(scanner.startsWith("\"") || scanner.startsWith("\'")) { // Color strings. char stringType = scanner[0]; output ~= "<span class=\"code_string\">"; int stringLength = 1; while(stringLength < scanner.length && scanner[stringLength] != stringType) { if(scanner[stringLength] == '\\') { stringLength += 1; } stringLength += 1; } } } return output; }
I'm imagining that my explanations would go here. I could discuss the code changes and snippets would appear inline.
And this would be the preview area

Tutorial

Let's use Javascript and HTML canvas to draw a little landscape scene.

Create the canvas and get access to the drawing context.

<canvas id="canvas" width="300" height="300">

const canvas = document.getElementById("canvas").getContext("2d")

Set some useful constants for later use.

const WIDTH = canvas.canvas.width
const HEIGHT = canvas.canvas.height
const TURNS = Math.PI * 2

First, fill the canvas with a solid background color.

canvas.fillStyle="#008"
canvas.fillRect(0,0, canvas.canvas.width, canvas.canvas.height)

Next, add a solid ground.

canvas.fillStyle="#080"
canvas.fillRect(0,canvas.canvas.height/2, canvas.canvas.width, canvas.canvas.height)

Next, a sun in the sky

canvas.fillStyle="#FF0"
canvas.strokeStyle="#880"
canvas.beginPath()
canvas.arc(.25 * WIDTH, .25 * HEIGHT, .1 * width, 0, 1 * TURNS);
canvas.fill();
canvas.stroke();

We need a happy little tree. First the trunk

canvas.fillStyle="#880"
canvas.fillRect(.75 * WIDTH, .5 * HEIGHT,.1 * WIDTH, .25 * HEIGHT) 

And the leaves

canvas.fillStyle="#0A0"
    canvas.beginPath()
    canvas.arc(.75 * WIDTH, .5 * HEIGHT, .1 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.85 * WIDTH, .5 * HEIGHT);
    canvas.arc(.85 * WIDTH, .5 * HEIGHT, .1 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.8 * WIDTH, .4 * HEIGHT);
    canvas.arc(.8 * WIDTH, .4 * HEIGHT, .1 * WIDTH, 0, 1 * TURNS);
    canvas.fill();

Let's add some clouds

    canvas.fillStyle="#ADF"
    canvas.beginPath()
    canvas.arc(.75 * WIDTH, .15 * HEIGHT, .05 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.8 * WIDTH, .1 * HEIGHT);
    canvas.arc(.8 * WIDTH, .1 * HEIGHT, .05 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.85 * WIDTH, .15 * HEIGHT);
    canvas.arc(.85 * WIDTH, .15 * HEIGHT, .05 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.9 * WIDTH, .1 * HEIGHT);
    canvas.arc(.9 * WIDTH, .1 * HEIGHT, .05 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.95 * WIDTH, .15 * HEIGHT);
    canvas.arc(.95 * WIDTH, .15 * HEIGHT, .05 * WIDTH, 0, 1 * TURNS);
    canvas.fill();

And some hills

    canvas.fillStyle="#070"
    canvas.beginPath()
    canvas.moveTo(.25 * WIDTH, 1 * HEIGHT);
    canvas.arc(.25 * WIDTH, .9 * HEIGHT, .5 * WIDTH, 0, 1 * TURNS);
    canvas.moveTo(.75 * WIDTH, .9 * HEIGHT);
    canvas.arc(.75 * WIDTH, .9 * HEIGHT, .5 * WIDTH, 0, 1 * TURNS);
    canvas.fill();

These hills are too flat. Let's add some shadows to them.

    canvas.fillStyle="#050"
    canvas.beginPath()
     canvas.arc(.25 * WIDTH, .9 * HEIGHT, .5 * WIDTH, -.25*TURNS, -.05 * TURNS);
     canvas.arc(.15 * WIDTH, .95 * HEIGHT, .55 * WIDTH, -.1 *TURNS, -0.20 * TURNS, true);
     canvas.moveTo(.75 * WIDTH, .4 * HEIGHT)
     canvas.arc(.75 * WIDTH, .9 * HEIGHT, .5 * WIDTH, -.25*TURNS, -.05 * TURNS);
     canvas.arc(.65 * WIDTH, .95 * HEIGHT, .55 * WIDTH, -.1 *TURNS, -0.20 * TURNS, true);     
    canvas.fill();
}

The sun is too low in the sky. Let's move it up.

canvas.fillStyle="#FF0"
canvas.strokeStyle="#880"
canvas.beginPath()
canvas.arc(.25 * WIDTH, .15 * HEIGHT, .1 * width, 0, 1 * TURNS);
canvas.fill();
canvas.stroke();

And add a sunbeam.

canvas.beginPath();
canvas.lineWidth = 2;
canvas.strokeStyle="#FF0"
canvas.moveTo(.4 * WIDTH, .15 * HEIGHT)
canvas.lineTo(.45 * WIDTH, .15 * HEIGHT)
canvas.stroke();

It's silly with just one.

canvas.beginPath();
canvas.lineWidth = 2;
canvas.strokeStyle="#FF0"
for(let i = 0; i < 23; i++) {
    let ang = i * TURNS / 23;
    let xOffsetScalar = Math.cos(ang);
    let yOffsetScalar = Math.sin(ang);
    canvas.moveTo((.25 + .12 * xOffsetScalar) * WIDTH, (.15 + .12 * yOffsetScalar) * HEIGHT)
    canvas.lineTo((.25 + .16 * xOffsetScalar) * WIDTH, (.15 + .16 * yOffsetScalar) * HEIGHT)
}
canvas.stroke();

And here's a shadow for the tree.

    canvas.fillStyle="#040"
    canvas.fillRect(.82 * WIDTH, .70 * HEIGHT,.3 * WIDTH, .05 * HEIGHT) 
    
    canvas.fillStyle="#450"
    canvas.fillRect(.81 * WIDTH, .5 * HEIGHT,.04 * WIDTH, .25 * HEIGHT) 

Let's remove the silly magic numbers

Sun3

const SUN_RADIUS = 0.1

canvas.fillStyle="#FF0"
canvas.strokeStyle="#880"
canvas.beginPath()
canvas.arc(.25 * WIDTH, .15 * HEIGHT, SUN_RADIUS * WIDTH, 0, 1 * TURNS);
canvas.fill();
canvas.stroke();

const SUN_BEAM_PADDING = 0.02
const SUN_BEAM_LENGTH = 0.04;
const SUN_BEAM_START_PCT = SUN_RADIUS + SUN_BEAM_PADDING
const SUN_BEAM_END_PCT = SUN_RADIUS + SUN_BEAM_PADDING + SUN_BEAM_LENGTH

canvas.beginPath();
canvas.lineWidth = 2;
canvas.strokeStyle="#FF0"

for(let i = 0; i < 23; i++) {
    let ang = i * TURNS / 23;
    let xOffsetScalar = Math.cos(ang);
    let yOffsetScalar = Math.sin(ang);
    canvas.moveTo((.25 + .SUN_BEAM_START_PCT * xOffsetScalar) * WIDTH, (.15 + SUN_BEAM_START_PCT * yOffsetScalar) * HEIGHT)
    canvas.lineTo((.25 + SUN_BEAM_END_PCT * xOffsetScalar) * WIDTH, (.15 + SUN_BEAM_END_PCT * yOffsetScalar) * HEIGHT)
}
canvas.stroke();
} 

That sun looks a little too big.

const SUN_RADIUS = 0.07





@*__table_of_contents__.

@*__index__.

Prev:WEB 5 Top:WEB 0 Next:WEB 7